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:
- Bump your Petal Pro version to 4.4.0 first
-
Then
mix deps.getpulls 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:
-<.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):
<.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).
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.
- Delete these two files:
lib/petal_pro_web/components/pro_components/aurora.ex
lib/petal_pro_web/components/pro_components/border_beam.ex
- Remove these imports:
- import PetalProWeb.Aurora
- import PetalProWeb.BorderBeam
- import PetalProWeb.BorderBeam
-
Port the
border_beamcall inauth_layout.exper the rename table (before and after above). -
If you used
<.aurora>anywhere, rename its attributes per the aurora table. -
Now
mix deps.update petal_componentsis 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_passkeystable plus apasskey_2fa_preferredboolean column on theuserstable -
Admin chat history -
admin_chat_conversationsandadmin_chat_messagestables 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:
-
Add the new config keys to
config/config.exs:
config :petal_pro, :webauthn,
enabled: true,
rp_name: "Your App Name",
rp_id: "yourdomain.com",
origin: "https://yourdomain.com"
-
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
-
-
Add the new passkey routes to your router (copy from the 4.1.0
router.ex). -
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:
-
Run
mix credo --strictand fix any violations it finds -
Get dialyzer running:
mix dialyzer(first run builds the PLT, takes a few minutes) - 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
-
Update your Petal Pro version in
mix.exs -
Run
mix deps.get -
Run
mix ecto.migrate(picks up the passkeys and admin chat tables) -
Run
mix credo --strictand fix violations -
Run
mix dialyzerand address any warnings - 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:
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:
# mix.exs
- {:httpoison, "~> 2.0"},
- {:tesla, "~> 1.8"},
+ {:req, "~> 0.5"},
# 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:
- 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 tables —
data_export_requestsanddata_deletion_logsfor the Your Data feature -
Notification preferences —
notification_preferencestable for per-type in-app/email toggles -
AI cost tracking —
ai_call_logstable for token counts, costs, and timing -
Changelog schema rename — renames underlying tables from
changelog_updates/changelog_likes/changelog_readstoupdates/likes/readswithin thechangelogcontext
Recommended upgrade steps
-
Update
mix.exs— bump Petal Pro version, swap LangChain/HTTPoison/Tesla for Jido/Req, update Styler to~> 1.11 -
Run
mix deps.get -
Run
mix ecto.migrate -
Run
mix format— Styler 1.11 will reformat a lot of code; review the diff but don’t worry about it -
Update
RESEND_API_KEYin your secrets/.envrc; remove old email adapter config - If you added custom LangChain tools: migrate them to the Jido tool behaviour pattern (see above)
-
If you used HTTPoison or Tesla directly: replace calls with
Req -
If you reference
PetalPro.ChangelogUpdates: update toPetalPro.Changelogand new schema names -
Optionally set
SENTRY_DSNandMCP_ADMIN_TOKENin 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:
- {: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):
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:
mix tailwind.install
Updated app.css
app.css has been updated to support CSS first configuration:
@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:
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:
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:
# 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:
# 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:
- 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:
- {: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.