Introduction to BeaconCMS: Building Your First Project

A designer's desk space with a laptop, model phones, and color charts.

Whether you need machine learning know-how, design to take your digital product to the next level, or strategy to set your roadmap, we can help. Book a free consult to learn more.

The world of Content Management Systems (CMS) is vast, and BeaconCMS stands out as an excellent option for Elixir developers who want a flexible, Phoenix-integrated CMS. If you’re looking for a straightforward way to manage your website’s content while staying within the Elixir ecosystem, this tutorial will guide you through the installation and basic usage of BeaconCMS.

In this blog post we will create the first version of a website called Elixir Tips, then we will make some changes to the website’s home page to learn how to interact with Beacon LiveAdmin.

What You’ll Learn:

  1. How to install and set up BeaconCMS in a Phoenix project.
  2. How to create content pages and customize layouts.

Prerequisites

Before we start, ensure you have the following:

  • Elixir v1.14 or later, Phoenix v1.7 or later and PostgreSQL installed on your machine.

1. Installing and Setting Up BeaconCMS

Let’s begin by setting up a new Phoenix application with Beacon and Beacon LiveAdmin installed. Follow these steps:

Step 1: Update Hex

mix local.hex

Step 2: Install or update Phoenix and Igniter Installers

mix archive.install hex phx_new

Check out the official Phoenix install guide for more info.

mix archive.install hex igniter_new

Igniter is an interesting open source tool used by BeaconCMS to automate the process of generating the necessary configurations for the correct functioning of BeaconCMS in your project. Check out the official docs for more info.

Step 3: Generate the new application

mix igniter.new elixir_tips --install beacon,beacon_live_admin --with phx.new

Remember to keep an eye out to accept the necessary changes that igniter will try to apply to the new Phoenix project.

And now the starndard steps to finish the Phoenix project instalation:

    $ cd elixir_tips

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

and you can start your Phoenix app with:

    $ mix phx.server

and visit http://localhost:4000 to see if the new project is working.

2. Creating Your First Site

Now that BeaconCMS is installed and configured, let’s create our first site to explore Beacon resources and learn the most common operations for managing your content.

Step 1: Generating a Site

Beacon provides a command to get your site up and running quickly. Open a terminal and run the following command:

mix beacon.gen.site --site elixir_tips --path /

Running this command modifies the files config/runtime.exs, application.ex, router.ex and also generates a migration file named create_beacon_tables.exs, but the order of the routes defined in router.ex are an important aspect of the site configuration, so it’s important for you to check the generated changes in the that file (see Beacon.Router for more info)

Step 2: Starting the application

Firstly execute the following to install all dependencies:

mix setup

And now start your Phoenix app:

mix phx.server

Visit http://localhost:4000 to see the default home page created by Beacon.

beacon home

3. Accessing LiveAdmin and managing your site

Before accessing the LiveAdmin page, let’s make a small change to your router.ex file. It’s important to correctly configure the route precedences. The line beacon_site "/", site: :elixir_tips is a catch-all route, so let’s invert the order by putting the admin scope first:

  scope "/" do
    pipe_through [:browser, :beacon_admin]
    beacon_live_admin "/admin"
  end

  scope "/", ElixirTipsWeb do
    pipe_through [:browser, :beacon]
    beacon_site "/", site: :elixir_tips
  end

Visit http://localhost:4000/admin and you should see the some menus for the elixir_tips site that we created listed on the admin interface.

admin home

Now let’s create the resources for our first site. We’ll start by creating the resources used by the home page before we customize its template.

Step 1: Create Assigns using the Live Data menu

We want to display the current year at the footer of the home page as the assign <%= @current_year %>. In Beacon we use Live Data to create and modify assigns at runtime, let’s do it.

Click in the Live Data button to access the Live Data page of your site (http://localhost:4000/admin/elixir_tips/live_data) and then click on the New Path button to create the new path /. Now use the New Live Data Assign button and to create a new assign named current_year. After creating it, click on it’s name, change the format to elixir and add the following value:

Date.utc_today().year

Save the changes.

Live Data Menu

Step 2: Handling events

Besides displaying some data, we will have a form to subscribe people to our newsletter, so we need to react to that event. Let’s create an event handler for that.

Click on the menu called Event Handlers and create a new event handler named join with the following content:

%{"waitlist" => %{"email" => email}} = event_params
IO.puts("#{email} joined the waitlist")
{:noreply, assign(socket, :joined, true)}

and then save the changes made.

Step 3: Uploading images

The last resource we want in our home is an image. Beacon provides a Media Library to upload and serve images. Go to the Media Library menu and you should see this page:

media library

As you can see, we already have an image, but let’s find another one and also upload it.

In this blog I’ll use this image: https://github.com/elixir-lang/elixir/blob/main/lib/elixir/pages/images/logo.png

image_upload

First we will use the image that’s already uploaded in beacon, and then we will make some changes to use the new image.

Step 4: Editing the Content

Finally, let’s update the home page template to make use of the live data, the event handler, and the image we just uploaded.

Click on the Pages menu and you should see a page called My Home Page already created for the / path. Click on its name to open the edit page.

Replace the template html code with this content:

<div class="relative flex min-h-[100dvh] flex-col overflow-hidden bg-gradient-to-br from-[#0077b6] to-[#00a8e8] text-white">
  <div class="absolute inset-0 z-[-1] bg-cover bg-center opacity-30 blur-[100px]"></div>
  <header class="container mx-auto flex items-center justify-between py-6 px-4 md:px-6">
    <div class="flex items-center gap-2">
      <.link patch="/" class="flex items-center gap-2">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="h-8 w-8"
        >
          <path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path>
          <line x1="4" x2="4" y1="22" y2="15"></line>
        </svg>
        <span class="text-2xl font-bold">CMS Platform</span>
      </.link>
    </div>
    <div class="flex items-center gap-4">
      <.link patch="/blog" class="hidden md:inline-flex text-sm font-medium hover:underline">
        Blog
      </.link>
      <button class="items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-secondary text-secondary-foreground hover:bg-secondary/80 h-10 px-4 py-2 hidden md:inline-flex">
        Sign In
      </button>
    </div>
  </header>
  <main class="container mx-auto flex-1 px-4 md:px-6">
    <div class="mx-auto max-w-6xl space-y-6 py-12 md:py-24 lg:py-32">
      <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
        <div class="space-y-6">
          <h1 class="text-4xl font-bold leading-tight md:text-5xl lg:text-6xl">
            <span class="bg-gradient-to-r from-[#00a8e8] to-[#0077b6] bg-clip-text text-[#333] dark:text-white">
              Unlock the power
            </span>
            of your content with our CMS platform
          </h1>
          <p class="text-lg text-gray-300 md:text-xl">
            Streamline your content management with our intuitive, high-performance CMS built on Phoenix LiveView.
          </p>
          <Phoenix.Component.form :let={f} for={%{}} as={:waitlist} phx-submit="join">
            <div class="flex w-full max-w-2xl items-center space-x-2">
              <input
                id={Phoenix.HTML.Form.input_id(f, :email)}
                name={Phoenix.HTML.Form.input_name(f, :email)}
                class="flex h-10 w-full border border-input text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 flex-1 rounded-md border-none bg-white/10 py-3 px-4 text-white placeholder:text-gray-300 focus:ring-2 focus:ring-[#00a8e8]"
                placeholder="Enter your email"
                type="email"
              />
              <button
                class="inline-flex items-center justify-center whitespace-nowrap text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 text-primary-foreground h-10 rounded-md bg-[#00a8e8] py-3 px-6 font-medium transition-colors hover:bg-[#0077b6]"
                type="submit"
              >
                Join Waitlist
              </button>
            </div>
          </Phoenix.Component.form>
          <span :if={Map.get(assigns, :joined)} class="text-sm text-gray-300">
            Congrats! You joined the watchlist.
          </span>
        </div>
        <div class="flex items-center justify-center">
          <.image
            site={@beacon.site}
            name="beacon.webp"
            alt="My Page Image"
            width="600"
            height="600"
            style="aspect-ratio: 600 / 600; object-fit: cover;"
          />
        </div>
      </div>
    </div>
  </main>
  <footer class="container mx-auto border-t border-white/20 py-6 px-4 text-sm text-gray-300 md:px-6">
    <div class="flex items-center justify-between">
      <p>© <%= @current_year %> CMS Platform. All rights reserved.</p>
      <div class="flex space-x-4 items-center">
        <a class="hover:underline" href="#">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="h-5 w-5"
          >
            <path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
          </svg>
          <span class="sr-only">Facebook</span>
        </a>
        <a class="hover:underline" href="#">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="h-5 w-5"
          >
            <path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z">
            </path>
          </svg>
          <span class="sr-only">Twitter</span>
        </a>
        <a class="hover:underline" href="#">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="h-5 w-5"
          >
            <rect width="20" height="20" x="2" y="2" rx="5" ry="5"></rect>
            <path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path>
            <line x1="17.5" x2="17.51" y1="6.5" y2="6.5"></line>
          </svg>
          <span class="sr-only">Instagram</span>
        </a>
        <a class="hover:underline" href="#">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="h-5 w-5"
          >
            <path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z">
            </path>
            <rect width="4" height="12" x="2" y="9"></rect>
            <circle cx="4" cy="4" r="2"></circle>
          </svg>
          <span class="sr-only">LinkedIn</span>
        </a>
        <a class="hover:underline" href="#">
          Privacy
        </a>
        <a class="hover:underline" href="#">
          Terms
        </a>
        <a class="hover:underline" href="#">
          Contact
        </a>
      </div>
    </div>
  </footer>
</div>

Save the changes and then click Publish to make these changes public. Return to your home page http://localhost:4000 to check the final result!

New template

You can the current year on the footer, that’s the Assign named current_year that we just defined.

You can type some email and click on the Join Waitlist button to check the code we put on the Event Handler working.

The code we put was simple as that:

%{"waitlist" => %{"email" => email}} = event_params
IO.puts("#{email} joined the waitlist")
{:noreply, assign(socket, :joined, true)}

We basically get the email the user typed, log some info and create the joined assignment. You can see this simple log message being logged in your running server after clicking on the Join Waitlist button.

Also, you could use the LiveAdmin again to manage this page of your site. Go back to it by visiting http://localhost:4000/admin and click on the Pages button, then My Home Page.

We can play around with the html content we put for this page. Try opening a new tab in your browser, access the home page again (http://localhost:4000), then return to the Live Admin tab and change the text that refers to the headline of the page (lines 28 to 36).

Replace with:

<h1 class="text-4xl font-bold leading-tight md:text-5xl lg:text-6xl">
  <span class="bg-gradient-to-r from-[#00a8e8] to-[#0077b6] bg-clip-text text-[#333] dark:text-white">
    Elixir Tips:
  </span>
  Your Daily Dose of Insights and Best Practices
</h1>
<p class="text-lg text-gray-300 md:text-xl">
  Level Up Your Elixir Skills, One Tip at a Time.
</p>

and to see the image you upload, change the line 59 with the image name you upload, in my case the image was named logo, so:

<.image site={@beacon.site} name="logo.webp" alt="My Page Image" width="600" height="600"
  style="aspect-ratio: 600 / 600; object-fit: cover;" />

Save and publish the page. Move to the home page tab and reload it; you should see the change you made being displayed.

template updated

One of the key advantages of using BeaconCMS is its ability to streamline content updates without requiring a full deployment cycle. As we can see in our example, it was simple to just publish an updated version of your page using Beacon LiveAdmin. Traditionally, updating a page in a Phoenix application would involve creating or modifying a file, submitting a pull request, waiting for CI checks, waiting for code review and approvals, and finally deploying the changes. With BeaconCMS, this process becomes significantly simpler. Content changes can be made directly through the CMS interface, allowing you to apply updates live and see the results instantly. This not only saves time but also reduces the friction associated with minor content adjustments, enabling a more agile and efficient workflow.

4. Next Steps

Now that you’ve set up BeaconCMS and created your first page, we can explore more Beacon features like the Visual Editor and also exploring creating components. Keep an eye out for the part two of this blog.

For more information, check out the official documentation for beaconCMS, beacon_live_admin, and also join the #beacon-cms channel on the Elixir Lang Slack.

Conclusion

In this tutorial, you’ve learned how to install and configure BeaconCMS in a Phoenix project, create basic pages, and customize layouts. BeaconCMS is a powerful tool that allows you to manage content within the Elixir ecosystem. Let’s dive deeper into its features in the next Beacon blog to see how it can help the development of dynamic websites.

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box