A human and robot hand drawing each other

Building your first custom GitHub action

Bekah Whittle
Bekah Whittle // Director, Field Services // GitHub

Last up in this module, we'll discuss the rationale for creating a custom GitHub Action and guide you through its construction.

We'll start by outlining the setup for your action.yml and index.js—the configuration blueprint and action logic, respectively—for a basic "Hello World" action. Additionally, we'll touch on best practices for releasing your action to GitHub Marketplace, including semantic versioning, and Datadog will offer some insight on the importance of listening to your customers during the process. Together, these steps offer a simplified approach to automating GitHub project workflows.


In this guide, you will learn how to:

  • Decide when to build a custom action

  • Write a simple but effective actions.yml file that outlines your action's metadata, including inputs and outputs

  • Connect your action to runtime logic by using specific parts of JavaScript code to fetch inputs and set outputs

  • Execute best practices for publishing your action to GitHub Marketplace by drafting a release and adopting semantic versioning


Decide when to build a custom action

Before diving into developing a custom action, weigh the choice between using existing actions in GitHub Marketplace versus building a custom action. This decision impacts not just your development efficiency but also your enterprise's governance and security posture.

Governance: Controlling your actions

How much control do you need or want over the actions your organization uses? GitHub allows you to set specific policies for action usage in your organization. Navigating to the "Actions General" section under your organization’s settings lets you restrict usage to actions created by GitHub or those from verified creators, which have undergone a vetting process and are generally considered safe.

Marketplace actions, particularly from verified creators, offer a key advantage: They are vetted by some of the industry's top security professionals. If governance and absolute control over code are paramount, however, custom actions developed in-house might be the way to go. You can store these in an "Actions" folder within your organization’s repositories, making it easier to manage and audit them.

Ease-of-use vs. customization

Marketplace actions are convenient, plug-and-play solutions for common tasks, but they may not always perfectly align with the needs of your enterprise. Custom actions, on the other hand, offer you the flexibility to tailor the executable code to meet your specific requirements and can be reused across various projects within your enterprise once they’re developed and vetted for security concerns. Innersourcing your actions across your enterprise is an excellent way to accelerate innovation while reducing risk. 

If you find that Marketplace actions are consistently close, but not quite right, there's also the middle-ground option of forking or cloning these actions and then modifying them. This provides the convenience of pre-built actions alongside the customizability of custom actions.

Keep in mind that creating an action isn’t one and done. Like any piece of software, it will require maintenance. Prioritize your efforts based on the platforms and systems your customers are already using. We didn't create our custom GitHub action at Datadog as an academic exercise. Our customers wanted a tool that integrated seamlessly into their existing continuous integration pipelines, many of which were already on GitHub. Our action met a real customer need.

Jake Pruitt
Jake Pruitt // Senior Engineer // Datadog

Even if you don’t end up building custom actions, understanding the process is useful, so let’s get going.

Write your action.yml file

To get started, create a new file named action.yml in your repository.

1. Establish your name and description.

Begin by specifying a name and description. This is what users, including yourself, will see when browsing through your enterprise's internal actions or Marketplace.

name: 'Hello World JS Action'
description: 'Greet someone and record the time'

2. Declare inputs.

Declare the action’s inputs. These will be parameters your workflows can pass to the action when using it. In our example, the input is who-to-greet.

inputs:
  who-to-greet:
    description: 'Who to greet'
    required: true
    default: 'World'

3. Outline outputs.

Outline what the action will output. This can be used in subsequent steps within the same job. For our simple action, it outputs the time of the greeting.

outputs:
  time:
    description: 'The time we greeted you'

4. Define the running environment.

Finally, specify the runtime environment and point to the JavaScript file that contains your action’s logic.

runs:
  using: 'node16'
  main: 'index.js'

That's it! You've set the stage for your JavaScript code to bring this action to life.

Building the logic

Once your action.yml file is in place, the next component is your JavaScript file, which in our case is index.js. This is where you'll embed the action’s operational logic. To connect your JavaScript code to the action, you'll use GitHub's own libraries—specifically, @actions/core and @actions/github.

const core = require('@actions/core');
const github = require('@actions/github');



try {
  // Fetch the value of the input 'who-to-greet' specified in action.yml
  const nameToGreet = core.getInput('who-to-greet');
  console.log(`Hello ${nameToGreet}!`);
  
  // Record the time of greeting as an output
  const time = (new Date()).toTimeString();
  core.setOutput("time", time);



} catch (error) {
  // Handle errors and indicate failure
  core.setFailed(error.message);
}

Here's a simplified breakdown of the JavaScript code, highlighting the portions essential for your action:

1. core.getInput('who-to-greet'): This fetches the value you defined as an input in action.yml, and we greet this entity in the action.

2. core.setOutput("time", time): This sets the output, specifically the time you made the greeting, which can be used in subsequent workflow steps.

3. try/catch block: This ensures easy handling of any exceptions, signaling a failure with core.setFailed(error.message) if needed.

That's it. With these elements in your index.js, your action is functional and ready for enterprise-level automation.

Releasing your action

After you've added the necessary files to your GitHub repository, a new prompt will appear at the top of your repository's code page, stating “Publish this Action to the GitHub Marketplace”. Clicking on this will direct you to the “Draft a new release” page tailored for Actions.

When releasing actions to Marketplace, keep in mind:

  • Semantic versioning: Utilize semantic versioning for your tag version. This helps simplify version control and is highly recommended for actions.

  • Description and categories: Make sure to provide a clear description for what the action does and select the category that best describes your action’s utility, be it Continuous Integration, AI, Chat, or something else.

  • Publish: After validating the details, go ahead and click Publish release. Your action will be available in Marketplace, tagged with the semantic version you specified.

Congratulations! You’ve joined Marketplace!

Up next: Advanced automation strategy with GitHub Actions module wrap-up

Now let’s review what we’ve learned so far and take a look at what else we can do with GitHub Actions.