How to build a Productive app for Make

Learn how to build a Productive app for Make in this step-by-step guide and tutorial

How to build a Productive app for Make
Do not index
Do not index
 
Video preview
 
In this guide, we will create a Productive app for Make. If you're a SaaS leader intrigued by expanding your software's accessibility to over 1500 third-party apps within the Make ecosystem, this guide provides a practical demonstration of the process. By the end, you'll know how to connect your software to Make.
 
Here’s what we’ll cover:
  1. What Make and Make apps are
  1. Why we’re building a Productive app
  1. How to set up a new app
  1. How to enable users to authorize with their own accounts
  1. How to configure the app’s base
  1. How to implement Remote Procedure Calls (RPCs)
  1. How to create an action module
  1. How to test your app
  1. How to publish your app
 

What is a Make app?

Make is a no-code platform with over 500,000 users across 198 countries that connects apps and moves data between them. Make apps are integrations that use third-party APIs and include modules. Modules are steps in a Make user’s automation. They can be actions, searches, triggers, aggregators, or iterators. By building an app for Make, users can incorporate your app into their automations on the Make platform.
 
 

Why build a Productive app for Make?

 
Productive serves marketing, creative, and software development agencies as an all-in-one management system. There isn’t currently a Make app for Productive making it a suitable candidate for our guide, especially since there are active requests for this integration on Make’s wishlist.
 
notion image
 
By integrating Productive with Make, users can automate tasks that currently require manual input. For example:
 
  • Automate Task Creation: Create Productive tasks from new Google Calendar events automatically
  • Project Kickoff Automation: Start new projects in Productive instantly when contracts are signed in PandaDoc
  • Streamlined HR Processes: Schedule employee time off directly from Slack messages, simplifying HR tasks and enhancing response times.
 

What we’ll build

This guide walks you through creating a basic version of a Productive app for Make.
 
notion image
 
The app we create will:
  • Select from your account’s managers, workflows, and companies to assign to the project.
  • Create a new project from within Make.
Before you start, make sure you have:
 

How to build a Productive app for Make: 7 steps

1. Create a new app

Log into Make, navigate to "My Apps," and select “create a new app”
 
notion image
 
Next, complete the form with your apps details.
notion image
 
Once done, we'll proceed to let users input their API keys and establish a connection.

2. Build the connection

Go to the "Connections" tab. Click "Create a New Connection" and choose "API Key" as the type. Label it "Productive".
notion image
Productive needs both the organization ID and the API token to authorize the connection.
 
notion image
 
In the “Parameters” tab, enter the organization ID and Auth Token. Users will provide both to connect Productive in Make.
 
[
    {
        "name": "organizationId",
        "type": "text",
        "label": "Organization Id",
        "required": true
    },
    {
        "name": "apiToken",
        "type": "text",
        "label": "Auth Token",
        "required": true
    }
]
 
Next, visit the “Communication” tab to set up account validation. Use Productive’s users endpoint to verify the API key. This confirms the API key's functionality and adds authorized user details to the connection label.
 
We also sanitize the auth token to remove sensitive information, protecting it from exposure in logs.
 
 
notion image
 
Paste the following code into the "Communication" tab to finalize the connection:
 
{
    // Request
    "url": "https://api.productive.io/api/v2/users", // Absolute URL to the API endpoint which validates credentials
    "headers": { // Additional HTTP headers
        "Content-Type": "application/vnd.api+json",
        "X-Auth-Token": "{{parameters.apiToken}}",
        "X-Organization-Id": "{{parameters.organizationId}}" 
    },

    // Response handling
    "response": {
        "metadata": { // Adds authorized user details to the connection label.
            "type": "email", // Type of the parameter. Can be "text" or "email".
            "value": "{{first(map(body.data, 'attributes.email'))}}"



        },
        "error": { // Error handling
            "message": "[{{statusCode}}] {{body.error}}" // On error, returns error message as "[statusCode] error text".
        }
    },

    "log": {
        "sanitize": [ // Excludes sensitive parameters from logs.
            "request.headers.X-Auth-Token" 
        ]
    }
}
 
Now, your connection tab is all set. Next, let's configure the base.
 

3. Configure the Base

 
The base holds all components that are common to all modules and remote procedures. Setting things up here ensures they are inherited by all modules and remote procedures, helping us avoid repetition.
We'll set up several key items:
  • BASE URL: This is the prefix for every API request made by our modules and remote procedures.
  • Headers: These are the default headers every module will use to authorize API requests, so we don't need to set them within each module.
  • Response: We define default directives for handling responses, like error management.
  • Logs: Default directives for handling logs include sanitizing sensitive data.
 
notion image
 
 
Here is the code to input into this section:
{
    "baseUrl": "https://api.productive.io/api/v2",
    "headers": {
        "Content-Type": "application/vnd.api+json",
        "X-Auth-Token": "{{connection.apiToken}}",
        "X-Organization-Id": "{{connection.organizationId}}"
    },
    // Updated response handling to extract status and detail from the first error
    "response": {

        "error": {

            "message": "{{join(map(body.errors; 'detail'); '; ')}}"

        }
    }
    ,
    "log": {
        "sanitize": [
            "request.headers.X-Auth-Token"
        ]
    }
}
 
Don’t forget to save your changes. Next, we’ll move on to our remote procedure calls, which will allow users to choose from existing projects, companies, and workflows in their Productive account.
 

4. Remote procedure calls (RPC)

Remote Procedure Calls (RPC) are functions that fetch additional data within a module. They are useful for fields that require dynamic data like IDs, which are hard for users to guess. This makes the user interface friendlier than requiring users to input static IDs.
 
notion image
We use RPCs for companies, people, and workflows to dynamically fetch these details for users when they start a new project. We prefetch these items to simplify user selection in the dropdown menu.
 

Create the RPC for companies

First, create the RPC for companies. Go to the "Remote Procedure Calls" tab, click "Create New Remote Procedure," and fill in the name and label as "rpcCompany" and select "Productive" for the connection.
 
notion image
 
Next, in the "Communication" tab, retrieve the list of companies from Productive's companies endpoint. We use pagination and limits to ensure the app loads quickly.
notion image
 
Paste the following code into the remote procedure section. This RPC does not accept parameters.

	{
	"url": "/companies",
	"method": "GET",
	"qs": { },
	"body": { },
	"headers": { },
	"response": {
		"limit": 15,
		"iterate": "{{body.data}}",
		"output": {
			"label": "{{item.attributes.name}}",
			"value": "{{item.id}}"
		}
	},
	"pagination": {
		"qs": {
			"page": "{{pagination.page}}"
		},
		"condition": "{{body.TotalPages >= pagination.page}}"
	}
}
 
 

Create the RPC for People

Next, let's set up a remote procedure call to fetch all people within the organization. This allows users to select someone from their organization to assign as a project manager. Go to the "Remote Procedure Calls" tab, click "Create New Remote Procedure," and fill in the name and label as "rpcPeople." Choose "Productive" for the connection.
 
notion image
 
In the "Communication" tab, retrieve the list of people from Productive's people endpoint. We also use pagination and limits to ensure the app loads quickly. This RPC does not require parameters, so leave that section blank.
 
notion image
 
Paste the following code into the "Communication" tab to complete this setup:
{
	"url": "/people",
	"method": "GET",
	"qs": { },
	"body": { },
	"headers": { },
	"response": {
		"limit": 15,
		"iterate": "{{body.data}}",
		"output": {
			"label": "{{item.attributes.first_name}} {{item.attributes.last_name}}",
			"value": "{{item.id}}"
		}
	},
	"pagination": {
		"qs": {
			"page": "{{pagination.page}}"
		},
		"condition": "{{body.TotalPages >= pagination.page}}"
	}
}
 
 

Create the RPC for workflows

For our last remote procedure call, we'll set up one for workflows. In Productive, new projects need to be linked to specific workflows, so we'll dynamically fetch this information for the user.
Go to the "Remote Procedure Calls" tab, click "Create New Remote Procedure," and enter "rpcWorkflows" for both the name and label. Choose "Productive" for the connection.
notion image
 
 
In the "COMMUNICATIONS" tab, access the list of workflows from Productive's workflow endpoint. We use pagination and limits to ensure the app loads efficiently. This RPC does not require any parameters.
notion image
 
Paste the following code into the "Communication" tab:

	{
	"url": "/workflows",
	"method": "GET",
	"qs": { },
	"body": { },
	"headers": { },
	"response": {
		"limit": 15,
		"iterate": "{{body.data}}",
		"output": {
			"label": "{{item.attributes.name}}",
			"value": "{{item.id}}"
		}
	},
	"pagination": {
		"qs": {
			"page": "{{pagination.page}}"
		},
		"condition": "{{body.TotalPages >= pagination.page}}"
	}
}
 
 
With this final step, we are now ready to create a module!

5. Create the action module

 
Go to the "MODULES" tab and create a new module.
 
notion image
 
Add mappable parameters, which allow variables from other modules to be included. This feature is particularly useful for creating multi-step apps. We will use the remote procedure calls we set up earlier.
 
 
notion image
 
In the "MAPPABLE PARAMATERS" tab, replace the existing parameters with the list below. Note that we are using a select type for Company ID, Project Manager, and Workflow ID:
 
[
    {
        "name": "name",
        "type": "text",
        "label": "Project Name",
        "required": true
    },
    {
        "name": "company_id",
        "type": "select",
        "label": "Company ID",
        "options": "rpc://rpcCompany",
        "required": true
    },
    {
        "name": "project_manager_id",
        "type": "select",
        "label": "Project Manager",
        "options": "rpc://rpcPeople",
        "required": true
    },
    {
        "name": "workflow_id",
        "type": "select",
        "label": "Workflow ID",
        "options": "rpc://rpcWorkflows",
        "required": true
    },
     {
        "name": "public_access",
        "type": "boolean",
        "label": "Access",
        "required": true
    }
]
 
Next, configure our code in the "COMMUNICATION" tab. We’ll make a request to Productive’s projects endpoint to create a new project.
 
notion image
 
 
Paste the following code into the "COMMUNICATION" tab:
{
	// Request to API endpoint.
	"url": "/projects", // Relative to base URL
	"method": "POST",
	"headers": { }, // Additional HTTP headers
	"qs": { }, // Query string
	"body": {
		"data": {
			"type": "projects",
			"attributes": {
				"name": "{{parameters.name}}",
				"project_type_id": 2,
				"public_access": "{{parameters.public_access}}"
			},
			"relationships": {
				"company": {
					"data": {
						"type": "companies",
						"id": "{{parameters.company_id}}"
					}
				},
				"project_manager": {
					"data": {
						"type": "people",
						"id": "{{parameters.project_manager_id}}"
					}
				},
				"workflow": {
					"data": {
						"type": "workflows",
						"id": "{{parameters.workflow_id}}"
					}
				}
			}
		}
	}
	,

	// Response handling
	"response": {
		"output": "{{body}}" // Returns API response body as an output bundle.
	}
}
 
Next, we'll generate our interface. The interface outlines the output structure and specifies the parameters used in the following modules. Make offers a tool for generating interfaces. You can find a sample response in Productive's documentation and paste it directly into Make’s interface generation tool.
 
 
notion image
 
 
Alternatively, you can paste the provided format into the interface tab.
Code to paste into the interface
// Defines JSON object with "id" parameter as expected API response body.

[
    {
        "name": "data",
        "type": "collection",
        "spec": [
            {
                "name": "id",
                "type": "text"
            },
            {
                "name": "type",
                "type": "text"
            },
            {
                "name": "attributes",
                "type": "collection",
                "spec": [
                    {
                        "name": "name",
                        "type": "text"
                    },
                    {
                        "name": "number",
                        "type": "text"
                    },
                    {
                        "name": "project_number",
                        "type": "text"
                    },
                    {
                        "name": "project_type_id",
                        "type": "number"
                    },
                    {
                        "name": "project_color_id",
                        "type": "text"
                    },
                    {
                        "name": "last_activity_at",
                        "type": "text"
                    },
                    {
                        "name": "public_access",
                        "type": "boolean"
                    },
                    {
                        "name": "time_on_tasks",
                        "type": "boolean"
                    },
                    {
                        "name": "tag_colors",
                        "type": "collection",
                        "spec": [ ]
                    },
                    {
                        "name": "archived_at",
                        "type": "text"
                    },
                    {
                        "name": "created_at",
                        "type": "text"
                    },
                    {
                        "name": "template",
                        "type": "boolean"
                    },
                    {
                        "name": "custom_fields",
                        "type": "text"
                    },
                    {
                        "name": "task_custom_fields_ids",
                        "type": "text"
                    },
                    {
                        "name": "sample_data",
                        "type": "boolean"
                    }
                ]
            },
            {
                "name": "relationships",
                "type": "collection",
                "spec": [
                    {
                        "name": "organization",
                        "type": "collection",
                        "spec": [
                            {
                                "name": "data",
                                "type": "collection",
                                "spec": [
                                    {
                                        "name": "type",
                                        "type": "text"
                                    },
                                    {
                                        "name": "id",
                                        "type": "text"
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "name": "company",
                        "type": "collection",
                        "spec": [
                            {
                                "name": "meta",
                                "type": "collection",
                                "spec": [
                                    {
                                        "name": "included",
                                        "type": "boolean"
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "name": "project_manager",
                        "type": "collection",
                        "spec": [
                            {
                                "name": "meta",
                                "type": "collection",
                                "spec": [
                                    {
                                        "name": "included",
                                        "type": "boolean"
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "name": "last_actor",
                        "type": "collection",
                        "spec": [
                            {
                                "name": "meta",
                                "type": "collection",
                                "spec": [
                                    {
                                        "name": "included",
                                        "type": "boolean"
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "name": "workflow",
                        "type": "collection",
                        "spec": [
                            {
                                "name": "meta",
                                "type": "collection",
                                "spec": [
                                    {
                                        "name": "included",
                                        "type": "boolean"
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "name": "custom_field_people",
                        "type": "collection",
                        "spec": [
                            {
                                "name": "meta",
                                "type": "collection",
                                "spec": [
                                    {
                                        "name": "included",
                                        "type": "boolean"
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "name": "template_object",
                        "type": "collection",
                        "spec": [
                            {
                                "name": "meta",
                                "type": "collection",
                                "spec": [
                                    {
                                        "name": "included",
                                        "type": "boolean"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ],
        "label": "Data"
    },
    {
        "name": "meta",
        "type": "collection",
        "spec": [ ],
        "label": "Meta"
    }
]
 
notion image
 
 
Our app is now complete! Let's give it a try!

6. Try it out

Let's put our app to the test! Start by navigating to the scenarios section.
 
notion image
 
 
 
Create a new scenario by clicking “Create a new scenario”
notion image
 
 
In the apps section of the scenario, choose "productive" and then select the "Create new project" module.
notion image
 
 
Now, create a new connection. Retrieve your organization ID from the settings tab in your Productive account under API integrations.
 
notion image
 
 
Generate a token, ensuring it includes read/write access.
notion image
 
Copy both the token and organization ID.
notion image
 
Paste the tokens into the connection form in your Make scenario, along with your organization ID, and save.
 
notion image
 
 
Fill out the form, click "OK", and run the scenario to ensure it functions properly.
notion image
 
 
 
Confirm by clicking "Yes" in the popup and selecting "Run Once".
notion image
 
 
If all goes well, you should find a new project in your Productive account under Project Management, then Projects.
 
notion image
 
Congratulations! You've successfully tested your Productive app!
 

7. Publishing

By default, our app is private and only accessible to us. If we want to share it with others, there are two options for making it public.
 
notion image
 
  • Published (Approval Not Requested): Share the app with users from any organization using the invitation link. Users who receive the share link must have administration rights or app install permissions granted to install and use the app in the scenario builder.
  • Published (Pending Approval): If you’d like the app to be available globally to all Make users, you’ll need to get Make’s approval. Once the app is submitted for approval, this status is assigned. It indicates that the app is under review and awaiting approval from Make’s review team.
For this tutorial, we can keep this app private, but it’s an important consideration as you build Make apps whether you want them to be available more globally.
 
⚠️
It's important to note that once the app is published, it cannot be made private again.
 
 

Conclusion

That's a wrap! While this example included one module, there are many ways to expand it.
  • Create trigger modules so users can create scenarios based on changes in their Productive accounts.
  • Update branding elements and add a custom logo.
Are you working on a Make app? If so, I'd love to hear about it!
 

We build third-party apps and integrations

Partner with us →

Written by

Lola
Lola

Lola is the founder of Lunch Pail Labs. She enjoys discussing product, SaaS integrations, and running a business. Feel free to connect with her on Twitter or LinkedIn.