Pro Upgrade Guide
Viewing latest docs.
Switch version: v3

Upgrade guide

How to upgrade your PetalPro project to the latest versions

Version updates can be a bit of a manual process. A boilerplate is meant to be a starting point for you to build from - rather than a framework like Phoenix. However, it is possible.

4.3.0 -> 4.4.0

Petal Pro 4.4.0 moves up to petal_components 4.6. The headline: two components you used to get from Petal Pro, aurora and border_beam, now live in the free petal_components library. They got rebuilt on the way over (glow, multiple beams, spring easing), so Pro drops its own copies and consumes the free ones like every other component. floating_div is gone too, replaced by the free popover.

If you take 4.4.0 the normal way, most of this is handled for you. The one thing that will bite is bumping petal_components on an older Pro.

Take Pro 4.4.0 before you run mix deps.update petal_components

Pro 4.4.0 requires petal_components ~> 4.6. If you update petal_components first, while still on an older Pro, the build dies with:

error: function border_beam/1 imported from both PetalProWeb.BorderBeam and PetalComponents.BorderBeam, call is ambiguous

Both libraries now export border_beam/1, both are in scope, so any unqualified call is ambiguous. aurora/1 is the same problem but quieter: Pro never calls <.aurora> itself, so the build stays green and it only breaks the first time you use <.aurora> in your own templates.

The fix is Pro 4.4.0, which deletes Pro’s copies and switches to the free ones. So the order is:

  1. Bump your Petal Pro version to 4.4.0 first
  2. Then mix deps.get pulls petal_components 4.6

If you never touched these two components, that is the whole job. If you customised them, you have a few attribute renames to do.

border_beam attribute renames

The free border_beam styles its own panel now (surface, border, radius), so on top of the renames you can drop the background, border and radius classes you used to pass alongside it.

Old (Pro) New (free 4.6) Notes
gradient_color_start color_from
gradient_color_end color_to
animation_duration duration
beam_size size
offset_distance initial_offset was a string like "0%", now an integer
border_color (removed) the beam draws its own border
border_radius border_radius still accepted, but leave it off and the beam follows your --pc-radius token

Pro’s only border_beam call site is the auth card in auth_layout.ex. Here is the before and after so you can see the class cleanup:

lib/petal_pro_web/components/pro_components/auth_layout.ex
-<.border_beam
-  gradient_color_start="#3b82f6"
-  gradient_color_end="#67e8f9"
-  border_radius="0.75rem"
-  animation_duration="10s"
-  class="border-gray-200! shadow-gray-200/50 bg-white px-5 py-8 shadow-lg dark:border-gray-700/80! dark:bg-gray-800/80 dark:shadow-none dark:backdrop-blur-sm sm:rounded-xl sm:px-10"
->
+<.border_beam
+  color_from="#3b82f6"
+  color_to="#67e8f9"
+  duration="10s"
+  class="shadow-gray-200/50 px-5 py-8 shadow-lg dark:shadow-none sm:px-10"
+>
   {render_slot(@inner_block)}
 </.border_beam>

aurora attribute renames

Old (Pro) New (free 4.6) Notes
invert invert was a boolean, now a string: "auto", "none", or "always"
animation_duration speed
background_gradient (removed) pass a colors list instead
aurora_gradient (removed) pass a colors list instead
opacity opacity unchanged
blur blur unchanged
mask_position mask_position unchanged
mask_coverage mask_coverage unchanged

Careful: unknown attributes fail silently

petal_components components pass anything they don’t recognise straight through to the DOM as a plain HTML attribute. So an old call like <.border_beam gradient_color_start="#3b82f6"> will not error after you switch to the free component. It renders a gradient_color_start attribute on the div, the browser ignores it, and your beam still shows up looking wrong. No compile error, no crash, just a component that quietly stopped matching your design. Work through the rename tables so you don’t ship one.

floating_div is gone

floating_div had no call sites left in Pro, so 4.4.0 deletes it along with its @floating-ui/dom npm dependency. If you used <.floating_div> in your own code you get a compile error on upgrade. Swap it for the free <.popover> in top-layer mode, which covers the same case (a panel that needs to escape a clipped or overflow-hidden container):

heex
<.popover top_layer placement="bottom-start">
  <:trigger>Open</:trigger>
  Your floating content
</.popover>

Catch this in your own app

The whole thing came from Pro shipping a component with the same name as one the free library exports. You can hit it the same way in your own code: define a component named the same as a petal_components one and every unqualified call becomes ambiguous. use PetalComponents names are a reserved namespace.

This test walks both sides at runtime and fails if any of your components shadow a use PetalComponents export. Drop it into your test suite. Two things to change for your app: swap :petal_pro for your OTP app name, and /petal_pro_web/components/ for wherever your components live (and adjust the @exempt list, or empty it).

elixir
defmodule MyAppWeb.PetalComponentsCollisionTest do
  use ExUnit.Case, async: true

  # Modules that render without `use PetalComponents` (e.g. email templates
  # on bare Phoenix.Component). Leave empty if you have none.
  @exempt [MyAppWeb.EmailComponents]

  @ignored_exports [:module_info]

  defp generated?(name) do
    name = Atom.to_string(name)
    String.starts_with?(name, "__") and String.ends_with?(name, "__")
  end

  test "no component shadows a name exported by `use PetalComponents`" do
    free = free_export_names()

    assert MapSet.size(free) > 50,
           "only found #{MapSet.size(free)} free exports - the import-block parser is probably broken"

    offenders =
      for mod <- app_component_modules(),
          mod not in @exempt,
          {name, arity} <- mod.module_info(:exports),
          name not in @ignored_exports,
          not generated?(name),
          MapSet.member?(free, name) do
        "#{inspect(mod)}.#{name}/#{arity}"
      end

    assert offenders == [], """
    Reserved-namespace collision. These components define names that
    `use PetalComponents` also exports, so any unqualified call is ambiguous:

    #{Enum.map_join(offenders, "\n", &"  - #{&1}")}

    If the free library now ships this component, delete your copy and move the
    call sites to the free one. If it is genuinely yours, rename it.
    """
  end

  defp free_export_names do
    for mod <- free_import_block(),
        Code.ensure_loaded?(mod),
        {name, _arity} <- mod.module_info(:exports),
        name not in @ignored_exports,
        not generated?(name),
        into: MapSet.new(),
        do: name
  end

  defp free_import_block do
    source =
      Mix.Project.deps_paths()
      |> Map.fetch!(:petal_components)
      |> Path.join("lib/petal_components.ex")
      |> File.read!()

    [_, block] = Regex.run(~r/import PetalComponents\.\{(.+?)\}/s, source)

    block
    |> String.split(",")
    |> Enum.map(&String.trim/1)
    |> Enum.reject(&(&1 == ""))
    |> Enum.map(&Module.concat(PetalComponents, &1))
  end

  defp app_component_modules do
    {:ok, mods} = :application.get_key(:petal_pro, :modules)

    Enum.filter(mods, fn mod ->
      Code.ensure_loaded?(mod) and
        function_exported?(mod, :__components__, 0) and
        mod |> source_path() |> String.contains?("/petal_pro_web/components/")
    end)
  end

  defp source_path(mod) do
    to_string(mod.module_info(:compile)[:source])
  rescue
    _ -> ""
  end
end

If you can’t pull 4.4.0

If your membership has lapsed you can still fix this by hand, no repo access needed. You are deleting Pro’s two component copies, dropping their imports, and porting the one call site.

  1. Delete these two files:
lib/petal_pro_web/components/pro_components/aurora.ex
lib/petal_pro_web/components/pro_components/border_beam.ex
  1. Remove these imports:
lib/petal_pro_web/components/pro_components.ex
-      import PetalProWeb.Aurora
-      import PetalProWeb.BorderBeam
lib/petal_pro_web/components/pro_components/auth_layout.ex
-  import PetalProWeb.BorderBeam
  1. Port the border_beam call in auth_layout.ex per the rename table (before and after above).

  2. If you used <.aurora> anywhere, rename its attributes per the aurora table.

  3. Now mix deps.update petal_components is safe.

That is the complete fix. The free components are a superset of what Pro shipped, so you lose nothing by switching.


4.0.x -> 4.1.0

New migrations

Run mix ecto.migrate after updating. Two new table groups are added:

  • WebAuthn passkeys - user_passkeys table plus a passkey_2fa_preferred boolean column on the users table
  • Admin chat history - admin_chat_conversations and admin_chat_messages tables for persistent AI chat

Both sets of migrations are additive - they won’t touch your existing data.

WebAuthn passkeys (optional)

Passkeys let your users register Face ID, Touch ID, or hardware keys. There are two modes: passwordless primary auth, and 2FA on top of password login. If you don’t want passkeys yet, skip this section - the migrations are still safe to run, the feature just won’t be reachable.

To wire up passkeys in an existing project:

  1. Add the new config keys to config/config.exs:
elixir
config :petal_pro, :webauthn,
  enabled: true,
  rp_name: "Your App Name",
  rp_id: "yourdomain.com",
  origin: "https://yourdomain.com"
  1. Copy the new passkey LiveViews from the 4.1.0 source into your project:

    • lib/petal_pro_web/live/auth/passkey_registration_live.ex
    • lib/petal_pro_web/live/auth/passkey_login_live.ex
    • lib/petal_pro_web/live/user_settings/passkey_settings_live.ex
  2. Add the new passkey routes to your router (copy from the 4.1.0 router.ex).

  3. The security settings page gains a “Passkeys” section automatically once the routes are in place.

Persistent admin chat history (optional)

Admin chat conversations are now stored in the DB and survive reloads and restarts. The sidebar lists recent conversations. This is purely additive - just run the migrations and you’re done, no config needed.

mix quality now runs credo –strict and dialyzer

This is the one change that may require some work on existing projects. Fresh clones of 4.1.0 pass both out of the box, but an existing project that’s been customised will likely have some credo violations to clean up.

Steps:

  1. Run mix credo --strict and fix any violations it finds
  2. Get dialyzer running: mix dialyzer (first run builds the PLT, takes a few minutes)
  3. Fix any dialyzer warnings that come up

Both tools are optional to run locally, but CI will run them via mix quality from 4.1.0 onwards. If you don’t want strict credo or dialyzer in your CI, update your mix quality alias in mix.exs to match your preferences.

Upgrade steps

  1. Update your Petal Pro version in mix.exs
  2. Run mix deps.get
  3. Run mix ecto.migrate (picks up the passkeys and admin chat tables)
  4. Run mix credo --strict and fix violations
  5. Run mix dialyzer and address any warnings
  6. If you want passkeys: add the config, copy the LiveViews, add the routes (see above)

3.0 -> 4.0

Breaking changes

Jido AI replaces LangChain for admin chat

The admin chat system has been rewritten around Jido. If you added custom LangChain tools, you’ll need to migrate them to the Jido tool behaviour pattern.

Each tool is a module that implements the PetalPro.Ai.Tool behaviour:

elixir
defmodule MyApp.Ai.Tools.MyTool do
  @behaviour PetalPro.Ai.Tool

  @impl true
  def name, do: "my_tool"

  @impl true
  def description, do: "Does something useful"

  @impl true
  def parameters do
    %{
      type: "object",
      properties: %{
        user_id: %{type: "string", description: "The user's ID"}
      },
      required: ["user_id"]
    }
  end

  @impl true
  def execute(%{"user_id" => user_id}, _context) do
    # your logic here
    {:ok, "result"}
  end
end

Register tools in your PetalPro.Ai.Tools registry module. Remove any LangChain dependencies from mix.exs.

Req replaces HTTPoison and Tesla

All HTTP calls now use Req. Replace any remaining HTTPoison or Tesla calls:

diff
# mix.exs
- {:httpoison, "~> 2.0"},
- {:tesla, "~> 1.8"},
+ {:req, "~> 0.5"},
elixir
# Before (HTTPoison)
HTTPoison.get!("https://example.com/api")

# After (Req)
Req.get!("https://example.com/api")

ChangelogUpdates context renamed to Changelog

The changelog context and its schema modules have been renamed:

diff
- PetalPro.ChangelogUpdates
+ PetalPro.Changelog

- PetalPro.ChangelogUpdates.ChangelogUpdate  ->  PetalPro.Changelog.Update
- PetalPro.ChangelogUpdates.ChangelogLike    ->  PetalPro.Changelog.Like
- PetalPro.ChangelogUpdates.ChangelogRead    ->  PetalPro.Changelog.Read

Update any references in your own code. A new migration handles the underlying table renames.

Styler upgraded from 0.11 to 1.11

Styler 1.x applies additional formatting rules. After updating deps, run mix format — expect a large diff of auto-reformatted code. This is cosmetic only.

Sidebar-only layout

The desktop header layout has been removed. All authenticated pages now use the sidebar layout exclusively. If you have pages using a header-based layout, migrate them to type="sidebar".

Email provider simplified to Resend

The email adapter is now Resend only. Set RESEND_API_KEY in your environment. Remove any SES or other adapter configuration.

New environment variables

Variable Required Purpose
RESEND_API_KEY Yes Transactional email via Resend
SENTRY_DSN No Auto-activates Sentry error monitoring when set
MCP_ADMIN_TOKEN Recommended Bearer token for the admin MCP server at POST /api/mcp

New migrations

Run mix ecto.migrate after updating. The new migrations cover:

  • GDPR tablesdata_export_requests and data_deletion_logs for the Your Data feature
  • Notification preferencesnotification_preferences table for per-type in-app/email toggles
  • AI cost trackingai_call_logs table for token counts, costs, and timing
  • Changelog schema rename — renames underlying tables from changelog_updates/changelog_likes/changelog_reads to updates/likes/reads within the changelog context

Recommended upgrade steps

  1. Update mix.exs — bump Petal Pro version, swap LangChain/HTTPoison/Tesla for Jido/Req, update Styler to ~> 1.11
  2. Run mix deps.get
  3. Run mix ecto.migrate
  4. Run mix format — Styler 1.11 will reformat a lot of code; review the diff but don’t worry about it
  5. Update RESEND_API_KEY in your secrets/.envrc; remove old email adapter config
  6. If you added custom LangChain tools: migrate them to the Jido tool behaviour pattern (see above)
  7. If you used HTTPoison or Tesla directly: replace calls with Req
  8. If you reference PetalPro.ChangelogUpdates: update to PetalPro.Changelog and new schema names
  9. Optionally set SENTRY_DSN and MCP_ADMIN_TOKEN in production

2.2 -> 3.0

Petal Pro has been upgraded to Tailwind 4. Some utilities have been removed or renamed. See the Tailwind upgrade guide for more information.

To upgrade to Tailwind 4 in your project, make sure you update mix.exs:

diff
-  {:tailwind, "~> 0.2", runtime: Mix.env() == :dev}
+  {:tailwind, "~> 0.3", runtime: Mix.env() == :dev}

And config.exs (note that the paths have changed and that you need 4.0.9 or above):

diff
config :tailwind,
-  version: "3.3.3",
+  version: "4.1.11",
   default: [
     args: ~w(
-      --config=tailwind.config.js
-      --input=css/app.css
-      --output=../priv/static/assets/app.css
+      --input=assets/css/app.css
+      --output=priv/static/assets/app.css
     ),
-    cd: Path.expand("../assets", __DIR__)
+    cd: Path.expand("..", __DIR__)
  ]

Don’t forget to run:

shell
mix tailwind.install

Updated app.css

app.css has been updated to support CSS first configuration:

app.css
@import "tailwindcss";

@source "../../deps/petal_components/**/*.*ex";
@import "../../deps/petal_components/assets/default.css";

@import "./colors.css";
@import "./combo-box.css";
@import "./editorjs.css";
@import "./animations.css";
@import "../node_modules/tippy.js/dist/tippy.css" layer(components);

@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
@plugin "@tailwindcss/aspect-ratio";
@plugin "./tailwind_heroicons.js";

...

To see the latest changes for 3.0.0, go to:

https://github.com/petalframework/petal_pro/tree/v3.0.0/assets/cssgithub.com

The following files have changed:

  • app.css
  • combo-box.css
  • editorjs.css

The following files have been added:

  • colors.css
  • animations.css
  • tailwind_heroicons.js

Finally, tailwind.config.js has been removed.

Now you should be able to follow the Tailwind upgrade guide to update the rest of your project. Running mix format should automatically arrange tailwind classes in your project.

2.0 -> 2.2

With Petal Components 2.0.6, the heroicons dependency has been replaced with a CSS-based approach. This is based on what has been implemented in the Phoenix repo - the reasoning is explained in this issue. To upgrade an existing Petal Pro project, add the dependency in mix.exs:

mix.exs
def deps do
  [
+    {:heroicons,
+      github: "tailwindlabs/heroicons",
+      tag: "v2.1.5",
+      app: false,
+      compile: false,
+      sparse: "optimized"},
  ]
end

This will download a copy of the source SVG files directly from GitHub (into deps/heroicons/optimized). Then the following code will process these files to create custom CSS classes:

assets/tailwind.config.js
const colors = require("tailwindcss/colors");
const plugin = require("tailwindcss/plugin");
+ const fs = require("fs");
+ const path = require("path");

module.exports = {
  plugins: [
+    // Embeds Heroicons (https://heroicons.com) into your app.css bundle
+    // See your `CoreComponents.icon/1` for more information.
+    //
+    plugin(function({matchComponents, theme}) {
+      let iconsDir = path.join(__dirname, "../deps/heroicons/optimized")
+      let values = {}
+      let icons = [
+        ["", "/24/outline"],
+        ["-solid", "/24/solid"],
+        ["-mini", "/20/solid"],
+        ["-micro", "/16/solid"]
+      ]
+      icons.forEach(([suffix, dir]) => {
+        fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {
+          let name = path.basename(file, ".svg") + suffix
+          values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
+        })
+      })
+      matchComponents({
+        "hero": ({name, fullPath}) => {
+          let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
+          let size = theme("spacing.6")
+          if (name.endsWith("-mini")) {
+            size = theme("spacing.5")
+          } else if (name.endsWith("-micro")) {
+            size = theme("spacing.4")
+          }
+          return {
+            [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
+            "-webkit-mask": `var(--hero-${name})`,
+            "mask": `var(--hero-${name})`,
+            "mask-repeat": "no-repeat",
+            "background-color": "currentColor",
+            "vertical-align": "middle",
+            "display": "inline-block",
+            "width": size,
+            "height": size
+          }
+        }
+      }, {values})
+    })
  ],
};

1.8 -> 2.0

Starting with 2.0, the Petal Framework has returned to Petal Pro. There is a discussion with regards to this change in the PR. The Petal Framework package will be supported in the long run - so there is no need to upgrade immediately. However, over time new features will be added to Petal Pro, rather than the Petal Framework package. If you upgrade to 2.0, you will no longer need to provide your Petal Framework license key. If you have generated a Dockerfile for deployment, then you can remove the following section:

Dockerfile
# Remove the Petal repo:
- RUN --mount=type=secret,id=PETAL_LICENSE_KEY \
-     mix hex.repo add petal https://petal.build/repo \
-       --fetch-public-key "SHA256:6Ff7LeQCh4464psGV3w4a8WxReEwRl+xWmgtuHdHsjs" \
-       --auth-key $(cat /run/secrets/PETAL_LICENSE_KEY)

In addition, the npm recipe has been merged. Petal Pro is still using esbuild, however, javascript libraries are now downloaded as an npm package (instead of depending on a CDN).

You may need to add nodejs and npm to your Dockerfile:

Dockerfile
# install build dependencies
+ RUN apt-get update -y && apt-get install -y build-essential git nodejs npm \
+     && apt-get clean && rm -f /var/lib/apt/lists/*_*

In the past mix assets.setup would install Tailwind CSS and esbuild. Tailwind is now installed with mix setup, esbuild is installed via npm with mix assets.deploy. You’ll need to remove:

Dockerfile
- RUN mix assets.setup

Finally, lib/petal_pro_web/petal_framework_translations.ex is no longer required. Instead, components like the DataTable can use Gettext directly.

1.6 -> 1.7

Starting in this release, we now use Styler to format our codebase. This means there will be a large amount of changes. To minimise merge or rebase c onflicts when upgrading, we recommend you swap out Recode with Styler first:

mix.exs
- {:recode, "~> 0.6", only: [:dev, :test]},
+ {:styler, "~> 0.11", only: [:dev, :test], runtime: false},

Then, replace the content of .credo.exs with the one in this repo.

Finally, run the formatter for Styler to take care of things:

Another change we recommend you make before upgrading, especially if you’ve made changes to your landing page, is to rename PetalProWeb.Components.LandingPage to PetalProWeb.LandingPageComponents, make sure you also rename the file from landing_page.ex to landing_page_components.ex.

Given the amount of changes in this release, we recommend you backup your branch, then use merge instead of rebase to bring your branch up to date.

1.5.2

When upgrading to this version, you will need to make your local hex aware of our hex repo. Please follow the instructions here (it involves using a license key unique to you).

Updating Petal Framework

Updating the Petal Framework package is as simple as updating any dependency - simply update the version in mix.exs.

Updating the boilerplate code

Here is some discussion amongst members in a Github issue.

Firstly, I’d use {rename_project, "~ 0.1.0"} to rename your project back to PetalPro if you renamed it to something else.

Personally, I’d use option 3.

Option 1 - Copy your files into a fresh project

Manual and time-consuming, but surgical.

If you haven’t changed too many existing files and added mostly new ones, you could start a fresh project with the latest Petal Pro and migrate over the work you’ve done from the previous project. This is likely the easiest route. If you want to keep your git history, then you could try copying the .git folder from the previous project into the new one.

Option 2 - Using git branches

With this option, you maintain a “pure” Petal branch in your repository with no other modifications. Then, you merge this branch into your main app when you upgrade.

Follow the instructions here.

Option 3 - Rebasing

Keep a branch in sync with the PetalPro repo, and when it updates, you rebase (replay) your commits on top of it.

Option 4 - Attempt a git merge

Option 5 - rsync

  • rsync -r –delete –exclude=.git ~/code/petal_pro-1.3.0/ ~/code/your-app
  • git checkout HEAD path/to/files/rsync/deleted/but/shouldnt/have (I used –delete with rsync so I’d know about files that were removed, for example, I think some of the org stuff was moved, and I don’t want cruft)
  • git add -p
  • Use rename_phoenix_project.sh to rename back to your app name

Option 6 - Use a diffing tool like Araxis merge

Comment from a Pro user:

The easiest way for me is to create a fresh project from the updated petal_pro, rename it to the same name as your existing project, and then run a diff tool like Araxis merge against the root directories. As long as you have mostly added your own files and not modified the template files much it doesn’t take too long. You can either merge the template changes into your existing project (better for git history) or merge your customizations into the fresh project.