Deploying Elixir: Creating Your Own Elixir Package

Deploying Elixir: Creating Your Own Elixir Package


13 min read

During your journey as an Elixir developer, there is going to come to a point where you might want to publish your own package. This has happened to me a couple of times already, here are a couple of packages I have published.

Equip with the knowledge of what I needed to do to publish my own package, I thought I would take you on the same journey. Let's get started.

Our first stop is going to be the Hex website. Hex has a great guide for publishing your own package that we will be following.

Registering a Hex User

Before we can do anything, we will need to register our own user. When registering a user, you will be prompted for a username, your email, and a password. The email is used to confirm your identity during signup, as well as to contact you in case there is an issue with one of your packages. The email will never be shared with a third party.

$ mix hex.user register

Now it’s time to decide on a name for your package. In this guide I will be creating a new Ueberauth package. If you were to go on and look at other Ueberauth packages, you notice there is a certain pattern followed. This will make the decision easy for us on what to call our Uberauth package that will implement the Patreon OAuth flow.


Generating Our Project

Now that we know the name of our package let’s generate the project

If you’re curious how to generate a standard mix project, please visit the Elixir site here:

mix new ueberauth_patreon

We should expect to see:

* creating
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/ueberauth_patreon.ex
* creating test
* creating test/test_helper.exs
* creating test/ueberauth_patreon_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd ueberauth_patreon
    mix test

Run "mix help" for more commands.

Let’s change the directory to our new project directory and let’s open the code in your editor of choice. For me I’ll be using Visual Studio Code, this is important later in the guide when I am making a test app.

Next, we will update the mix.exs file to have all the settings we know to start things off. I follow this section of the Hex publishing guide

def project do
      app: :ueberauth_patreon,
      description: "Ueberauth strategy for Patreon OAuth.",
      version: "1.0.0",
      elixir: "~> 1.13",
      source_url: "",
      homepage_url: "",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      package: [
        links: %{"GitHub" => ""},
        licenses: ["MIT"],

I decided to set my package version at 1.0.0 but feel free to start it at 0.0.1 since its your first version ever.

Adding Documentation Generation

If you follow along in the Hex guide, they will suggest using ex_doc and there is no reason not to. It generates some beautiful documentation.

Following the HexDoc’s guide Let’s add it to our project dependencies in our mix.exs file.

def deps do
        ## Other deps ...
    {:ex_doc, "~> 0.27", only: :dev, runtime: false},

Dont forget to install our new dependency.

mix deps.get

Now let's generate our docs to make sure everything is set up correctly

> mix docs
Generating docs...
View "html" docs at "doc/index.html"
View "epub" docs at "doc/ueberauth_patreon.epub"

If you have the latest NPM installed you can run the following command to see your docs in the browser:

npx serve doc/

This will kick off a little webserver without having to go install anything locally.

Let’s Get Coding

When creating a Ueberauth package, the project needs to be structured in a particular way. If you click on any of the listed strategies Ueberauth has you will get a lot of good examples of what you need to do. You can structure your project any way you like but in the case of Ueberauth it’s best we follow their expected structure.

Inside of lib, we need to create several files and folders.

mkdir lib/ueberauth
mkdir lib/ueberauth/strategy
touch lib/ueberauth/strategy/patreon.ex
mkdir lib/ueberauth/strategy/patreon
touch lib/ueberauth/strategy/patreon/oauth.ex

Since we're making an Ueberauth strategy we need to make sure to install Ueberauth and OAuth2 packages. The mix.exs file should start looking like this now.

defp deps do
      {:ueberauth, "~> 0.7"},
      {:oauth2, "~> 2.0"},
      {:ex_doc, "~> 0.27", only: :dev, runtime: false}

Now let’s install those new dependencies.

mix deps.get

Now it's time to implement what we want our package to do. I am not going to explain how to implement an Uberauth package in this article, you can read my tutorial Creating Your Own Ueberauth Strategy on how. Instead, we'll just be copying and pasting some code in.

Inside lib/ueberauth/strategy/patreon.ex add the following:

defmodule Ueberauth.Strategy.Patreon do
  use Ueberauth.Strategy,
    oauth2_module: Ueberauth.Strategy.Patreon.OAuth

  alias Ueberauth.Auth.Info
  alias Ueberauth.Auth.Credentials
  alias Ueberauth.Auth.Extra

  @doc """
  Handles the initial redirect to the patreon authentication page.

  To customize the scope (permissions) that are requested by patreon include
  them as part of your url:

  def handle_request!(conn) do
    scopes = conn.params["scope"] || option(conn, :default_scope)

    params =
      [scope: scopes]
      |> with_state_param(conn)

    module = option(conn, :oauth2_module)
    redirect!(conn, apply(module, :authorize_url!, [params]))

  @doc """
  Handles the callback from Patreon.

  When there is a failure from Patreon the failure is included in the
  `ueberauth_failure` struct. Otherwise the information returned from Patreon is
  returned in the `Ueberauth.Auth` struct.
  def handle_callback!(%Plug.Conn{params: %{"code" => code}} = conn) do
    module = option(conn, :oauth2_module)
    token = apply(module, :get_token!, [[code: code]])

    if token.access_token == nil do
      set_errors!(conn, [
        error(token.other_params["error"], token.other_params["error_description"])
      fetch_user(conn, token)

  @doc false
  def handle_callback!(conn) do
    set_errors!(conn, [error("missing_code", "No code received")])

  @doc """
  Cleans up the private area of the connection used for passing the raw Notion
  response around during the callback.
  def handle_cleanup!(conn) do
    |> put_private(:patreon_token, nil)
    |> put_private(:patreon_user, nil)

  @doc """
  Fetches the uid field from the Twitch response. This defaults to the option `uid_field` which in-turn defaults to `id`
  def uid(conn) do
    %{"data" => user} = conn.private.patreon_user

  @doc """
  Includes the credentials from the Patreon response.
  def credentials(conn) do
    token = conn.private.patreon_token

      token: token.access_token,
      token_type: token.token_type,
      refresh_token: token.refresh_token,
      expires_at: token.expires_at

  @doc """
  Fetches the fields to populate the info section of the `Ueberauth.Auth`
  def info(conn) do
    %{ "data" => %{
      "attributes" => %{
        "full_name" => full_name,
        "first_name" => first_name,
        "last_name" => last_name,
        "about" => about,
        "image_url" => image_url,
        "url" => url,
        "email" => email
    }} = conn.private.patreon_user

      email: email,
      name: full_name,
      first_name: first_name,
      last_name: last_name,
      description: about,
      image: image_url,
      urls: %{
        profile: url

  @doc """
  Stores the raw information (including the token) obtained from the Patreon
  def extra(conn) do
      raw_info: conn.private.patreon_user

  defp fetch_user(conn, token) do
    conn = put_private(conn, :patreon_token, token)

    case Ueberauth.Strategy.Patreon.OAuth.get(
         ) do
      {:ok, %OAuth2.Response{status_code: 401, body: _body}} ->
        set_errors!(conn, [error("token", "unauthorized")])

      {:ok, %OAuth2.Response{status_code: status_code, body: user}}
      when status_code in 200..399 ->
        put_private(conn, :patreon_user, user)

      {:error, %OAuth2.Error{reason: reason}} ->
        set_errors!(conn, [error("OAuth2", reason)])

      {:error, %OAuth2.Response{body: %{"message" => reason}}} ->
        set_errors!(conn, [error("OAuth2", reason)])

      {:error, _} ->
        set_errors!(conn, [error("OAuth2", "uknown error")])

  defp option(conn, key) do
    Keyword.get(options(conn), key, Keyword.get(default_options(), key))

Inside lib/ueberauth/strategy/patreon/oauth.ex paste the following code:

defmodule Ueberauth.Strategy.Patreon.OAuth do
  @moduledoc """
  An implementation of OAuth2 for Patreon.

  To add your `:client_id` and `:client_secret` include these values in your

      config :ueberauth, Ueberauth.Strategy.Patreon.OAuth,
        client_id: System.get_env("PATREON_CLIENT_ID"),
        client_secret: System.get_env("PATREON_CLIENT_SECRET")

  use OAuth2.Strategy

  @defaults [
    strategy: __MODULE__,
    site: "",
    authorize_url: "",
    token_url: "",
    token_method: :post

  @doc """
  Construct a client for requests to Patreon.

  Optionally include any OAuth2 options here to be merged with the defaults:

        redirect_uri: "http://localhost:4000/auth/patreon/callback"

  This will be setup automatically for you in `Ueberauth.Strategy.Patreon`.

  These options are only useful for usage outside the normal callback phase of
  def client(opts \\ []) do
    config =
      |> Application.fetch_env!(Ueberauth.Strategy.Patreon.OAuth)
      |> check_credential(:client_id)
      |> check_credential(:client_secret)
      |> check_credential(:redirect_uri)

    client_opts =
      |> Keyword.merge(config)
      |> Keyword.merge(opts)

    json_library = Ueberauth.json_library()
    |> OAuth2.Client.put_serializer("application/json", json_library)
    |> OAuth2.Client.put_serializer("application/vnd.api+json", json_library)

  @doc """
  Provides the authorize url for the request phase of Ueberauth.

  No need to call this usually.
  def authorize_url!(params \\ [], opts \\ []) do
    |> client
    |> OAuth2.Client.authorize_url!(params)

  def get(token, url, headers \\ [], opts \\ []) do
    |> put_header("authorization", "Bearer " <> token)
    |> put_header("accept", "application/json")
    |> OAuth2.Client.get(url, headers, opts)

  def get_token!(params \\ [], options \\ []) do
    headers = Keyword.get(options, :headers, [])
    options = Keyword.get(options, :options, [])

    client_options = Keyword.get(options, :client_options, [])

    client = OAuth2.Client.get_token!(client(client_options), params, headers, options)


  # Strategy Callbacks
  def authorize_url(client, params) do
    OAuth2.Strategy.AuthCode.authorize_url(client, params)

  def get_token(client, params, headers) do
    client =
      |> put_param("grant_type", "authorization_code")
      |> put_header("Accept", "application/json")

    OAuth2.Strategy.AuthCode.get_token(client, params, headers)

  defp check_credential(config, key) do
    check_config_key_exists(config, key)

    case Keyword.get(config, key) do
      value when is_binary(value) ->

      {:system, env_key} ->
        case System.get_env(env_key) do
          nil ->
            raise "#{inspect(env_key)} missing from environment, expected in config :ueberauth, Ueberauth.Strategy.Patreon"

          value ->
            Keyword.put(config, key, value)

  defp check_config_key_exists(config, key) when is_list(config) do
    unless Keyword.has_key?(config, key) do
      raise "#{inspect(key)} missing from config :ueberauth, Ueberauth.Strategy.Patreon"


  defp check_config_key_exists(_, _) do
    raise "Config :ueberauth, Ueberauth.Strategy.Patreon is not a keyword list, as expected"

It’s always a great idea to add doc headers to all your public functions you expect your package users to be using. This will help them immensely in figuring out what each function does and seeing examples of input and output. For this package, a user won't be using it directly but instead following specific setup steps, so our documentation reflects that.

Generate our documentation to see how things look now.

mix docs

And spin up a server to see how things.

npx serve docs/

Bonus: Add the README

This is more of a personal preference than anything else. I'm not sure if you noticed when browsing the Hex Docs for various packages. But sometimes you will come across a project that has pretty nice docs but the setup steps will be missing. If you actually visit the packages repository, you will find a great README with all the information on how to setup up the project.

GAHHHH I also need those steps in those docs! Well, you easily can tell ex_doc to include the README. Let’s update our project settings to do just that.

In our mix.exs file add the docs field, it should look something like this now.

def project do
      app: :ueberauth_patreon,
      description: "Ueberauth strategy for Patreon OAuth.",
      version: "1.0.0",
      elixir: "~> 1.13",
      source_url: "",
      homepage_url: "",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      package: [
        links: %{"GitHub" => ""},
        licenses: ["MIT"],
      docs: [ #### New docs field
        extras: [""] ### This is going to include the readme in the docs

Now let’s go to our README and update it with the project setup steps.

# Überauth Patreon

[![Hex Version](](

> Patreon OAuth2 strategy for Überauth.

## Installation

1. Setup your application in Patreon Development Dashboard

1. Add `:ueberauth_patreon` to your list of dependencies in `mix.exs`:

    def deps do
      [{:ueberauth_patreon, "~> 1.0"}]
  1. Add Patreon to your Überauth configuration:

     config :ueberauth, Ueberauth,
       providers: [
         patreon: {Ueberauth.Strategy.Patreon, [default_scope: "identity[email] identity"]},
  2. Update your provider configuration:

    config :ueberauth, Ueberauth.Strategy.Patreon.OAuth,
      client_id: System.get_env("PATREON_CLIENT_ID"),
      client_secret: System.get_env("PATREON_CLIENT_SECRET"),
      redirect_uri: System.get_env("PATREON_REDIRECT_URI")
  3. Include the Überauth plug in your router pipeline:

    defmodule TestPatreonWeb.Router do
      use TestPatreonWeb, :router
      pipeline :browser do
        plug Ueberauth
  4. Add the request and callback routes:

    scope "/auth", TestPatreonWeb do
      pipe_through :browser
      get "/:provider", AuthController, :request
      get "/:provider/callback", AuthController, :callback
  5. Create a new controller or use an existing controller that implements callbacks to deal with Ueberauth.Auth and Ueberauth.Failure responses from Patreon.

       defmodule TestPatreonWeb.AuthController do
         use TestPatreonWeb, :controller
         def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do
           |> put_flash(:error, "Failed to authenticate.")
           |> redirect(to: "/")
         def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
           case UserFromAuth.find_or_create(auth) do
             {:ok, user} ->
               |> put_flash(:info, "Successfully authenticated.")
               |> put_session(:current_user, user)
               |> configure_session(renew: true)
               |> redirect(to: "/")
             {:error, reason} ->
               |> put_flash(:error, reason)
               |> redirect(to: "/")


Once your setup, you can initiate auth using the following URL, unless you changed the routes from the guide:



The docs can be found at ueberauth_patreon on Hex Docs.

Generate our documentation to see how things look now.

mix docs

And spin up a server to see how things.

npx serve docs/

Our new package is code complete but we don't know if it actually works.

Testing Our Package In A Project

I think I write half-decent code, but not enough to trust that it just works so I want to try testing it in an actual Phoenix project. Let’s spin one up to test it LIVE.

mix test_patreon

Short Detour: Using VS Code Run Our Project

We will need a database for our project, the easiest way I like to do this is using Visual Studio Code’s Remote - Containers feature. Using Docker and an existing template, I can spin up a dev environment that all the fixings I need to work on a Phoenix project.

  1. Start VS Code, run the Remote-Containers: Open Folder in Container... command from the Command Palette (F1) or quick actions Status bar item, and select the project folder you would like to set up the container for.

  2. Now select Show All Definitions... > Elixir, Phoenix, Node.js & PostgresSQL (Community)

  3. After picking the starting point for your container, VS Code will add the dev container configuration files to your project (.devcontainer/devcontainer.json).

  4. The VS Code window will reload and start building the dev container. A progress notification provides status updates. You only have to build a dev container the first time you open it; opening the folder after the first successful build will be much quicker.

  5. After the build completes, VS Code will automatically connect to the container.

Now your project is all set up with everything you need to run the project. You will most likely need to go into the docker-compose.yml file and update the database name to the name your project will be using.

Resuming: Testing Our Package In A Project

Let make sure our project setup works.

mix setup

In order to test our project, we don’t need to worry about publishing it to Hex. We can install it locally or install it through git. I have already pushed my project to Github so I want to show you how you can install any package hosted on Git or Github really easily.

Add the following to your deps. Your name or URL would be different if your project.

{:ueberauth_patreon, github: "talk2MeGooseman/ueberauth_patreon"}

As a personal rule of thumb, make sure to go through the setup guide you have for your package VERBATIM. This ensures you didn't miss any steps as part of the setup guide, because were the author sometimes we can accidentally gloss over an important.

Now install your package and test it out.

mix deps.get

Here is to hoping your project just works during testing it!

UH OH, My Package Has A Bug!

When the inevitable and your find a bug in your code there are a couple of things you can do.

  1. Make updates to your package project code and push the changes to Git. If you do this, in your test project make sure to run the following command to install the latest updates.
mix deps.update ueberauth_patreon
  1. Modify the package code directly inside your test project. All your installed packages can be found inside your deps/ folder and any changes you make to them can be recompiled so you can see how they affect your project. This can save you the trouble of having to go back to your package project, make code changes, and then push the code. Just run the following command to recompile your package.
mix deps.compile ueberauth_patreon

Make sure you copy the fixes you make back to your package project and commit them!

Submitting The Package

Now a package is complete and is ready to publish. Hex again does a great job detailing the steps you need to go through here

Just run the command and confirm when you verify everything looks good.

> mix hex.publish
Building ueberauth_patreon 1.0.0
    ueberauth ~> 0.7 (app: ueberauth)
    oauth2 ~> 2.0 (app: oauth2)
  App: ueberauth_patreon
  Name: ueberauth_patreon
  Version: 1.0.0
  Build tools: mix
  Description: Ueberauth strategy for Patreon OAuth.
  Licenses: MIT
  Elixir: ~> 1.13
Before publishing, please read the Code of Conduct:

Publishing package to public repository hexpm.

Oh No I Messed Up My Docs!

Free not, for Hex has a way to directly publish doc updates. No need to make a new release just to update docs. Once you have made your docs updates just run the following command and you are good.

mix hex.publish docs