<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Joel Rainwater</title>
    <description>The latest articles on Forem by Joel Rainwater (@rain2o).</description>
    <link>https://forem.com/rain2o</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F164153%2Facdb4505-57a2-4da1-b0ff-9ac1438873d5.jpeg</url>
      <title>Forem: Joel Rainwater</title>
      <link>https://forem.com/rain2o</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rain2o"/>
    <language>en</language>
    <item>
      <title>Web Developer Tools Fundamentals</title>
      <dc:creator>Joel Rainwater</dc:creator>
      <pubDate>Sat, 02 Jul 2022 07:59:29 +0000</pubDate>
      <link>https://forem.com/rain2o/web-developer-tools-fundamentals-56ih</link>
      <guid>https://forem.com/rain2o/web-developer-tools-fundamentals-56ih</guid>
      <description>&lt;p&gt;You know how to write code... now what?&lt;/p&gt;

&lt;p&gt;A common challenge new developers run into is making the transition from writing code in a closed environment, such as a tutorial or course, to building a real project. Suddenly there's a whole new world of tools that are mentioned but not explained.&lt;/p&gt;

&lt;p&gt;Perhaps you are trying to set up your first development project on your local and don't know where to start. Or maybe you're reading a tutorial or some documentation and it says "Prerequisite: npm/yarn, webpack, hadron collider, etc...". Ok, maybe not that last one, but you get the idea.&lt;/p&gt;

&lt;p&gt;WTF is &lt;code&gt;npm&lt;/code&gt; and how do I use it? How do I set up a local environment, deploy to a server, or share my code with other developers?&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I am Joel, a self-taught web developer for over 10 years. As a self-taught developer, and a mentor for new developers, I have both experienced these challenges and hear these questions often from those who are learning to code.&lt;/p&gt;

&lt;p&gt;I've noticed that there are a lot of code adjacent things that are not well covered, or just expected or assumed knowledge in a lot of documentation, tutorials, and courses.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to expect
&lt;/h2&gt;

&lt;p&gt;I will be writing a series of articles in an attempt to break down these popular tools and processes in an understandable way.&lt;/p&gt;

&lt;p&gt;I won't be going into tedious depth, this is intended for beginners to get a grasp on the basics. If you'd like to learn something more in-depth let me know and I'll do what I can in a separate article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subjects covered
&lt;/h2&gt;

&lt;p&gt;Below are the subjects I intend to cover. &lt;strong&gt;Note&lt;/strong&gt; - some subjects will be specific to a particular ecosystem (e.g. &lt;code&gt;npm&lt;/code&gt; for JavaScript projects) while others will be more widely applicable (e.g. &lt;code&gt;git&lt;/code&gt; for any code project).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git&lt;/code&gt; (and the difference between &lt;code&gt;git&lt;/code&gt; and GitHub)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm&lt;/code&gt;/&lt;code&gt;yarn&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CI/CD&lt;/li&gt;
&lt;li&gt;webpack + babel&lt;/li&gt;
&lt;li&gt;Browser vs Client side code&lt;/li&gt;
&lt;li&gt;HTTP calls (APIs, database queries, etc...)&lt;/li&gt;
&lt;li&gt;Servers / hosting&lt;/li&gt;
&lt;li&gt;CSS preprocessors&lt;/li&gt;
&lt;li&gt;JavaScript modules (ESM, CJS) and import/export&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;If there are any subjects you'd like me to cover please post a comment below to let me know!&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow along
&lt;/h2&gt;

&lt;p&gt;I'll be writing and posting these over time and including them in a series so that this article, and all articles in the series, will update with links to each new article once published.&lt;/p&gt;

&lt;p&gt;Subscribe to my posts here if you'd like to be notified when a new article drops. And let me know your thoughts!&lt;/p&gt;

&lt;p&gt;See you soon...&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Using Mixpanel via proxy with Next.js rewrites</title>
      <dc:creator>Joel Rainwater</dc:creator>
      <pubDate>Sat, 26 Mar 2022 19:22:36 +0000</pubDate>
      <link>https://forem.com/rain2o/using-mixpanel-via-proxy-with-nextjs-rewrites-130e</link>
      <guid>https://forem.com/rain2o/using-mixpanel-via-proxy-with-nextjs-rewrites-130e</guid>
      <description>&lt;h2&gt;
  
  
  Quick Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://mixpanel.com"&gt;Mixpanel&lt;/a&gt; has great documentation for setting up your project in many different languages, including JavaScript. They also provide docs and examples for implenting tracking with a proxy. Setting up Mixpanel through a proxy is useful to bypass ad and tracking blockers, and is a nice way to keep all client requests through your domain.&lt;/p&gt;

&lt;p&gt;I didn't see any great resources out there for setting up Mixpanel specifically in &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; using their built-in Rewrites feature to accomplish the same goal as setting up a proxy. Using rewrites, I was able to keep all Mixpanel requests going through the same domain as my website/app, and didn't need to deal with configuring Nginx myself (I'm hosting on Vercel, so I don't normally have to touch webserver config).&lt;/p&gt;

&lt;p&gt;This is how I implemented that setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Mixpanel's JS SDK
&lt;/h2&gt;

&lt;p&gt;We are going to use Mixpanel's JS SDK &lt;code&gt;mixpanel-browser&lt;/code&gt;. You can install it with npm/yarn as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# npm&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; mixpanel-browser
&lt;span class="c"&gt;# yarn&lt;/span&gt;
yarn add mixpanel-browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using TypeScript, you can also install &lt;code&gt;@types/mixpanel-browser&lt;/code&gt; to have typings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create Mixpanel wrapper function
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; - This post is to explain the setup for using Next rewrites. I used &lt;a href="https://medium.com/@andrewoons/setting-up-mixpanel-in-react-3e4c5b8c2a36"&gt;this article&lt;/a&gt; as a guide for creating a basic Mixpanel wrapper function.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;mixpanel.{ts|js}&lt;/code&gt; wherever it makes sense in your project. We will be defining an object which will be imported and used anywhere you plan to implement Mixpanel tracking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Remove { Dict, Query } if not using TypeScript&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mixpanel-browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isProd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_MIXPANEL_TOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Use your project's URL, adding a slug for all Mixpanel requests&lt;/span&gt;
  &lt;span class="na"&gt;api_host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yourdomain.com/mp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Mixpanel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;track_links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;track_links&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;referrer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;referrer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;people&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we import the &lt;code&gt;mixpanel-browser&lt;/code&gt; library.&lt;/p&gt;

&lt;p&gt;When in initialize it, we specify our own &lt;code&gt;api_host&lt;/code&gt;. This is where we tell Mixpanel to use our URL instead of theirs. This url should be the same as your Next.js project's URL, with a specific slug which will be dedicated to only Mixpanel requests (e.g. &lt;code&gt;/mp&lt;/code&gt;). You can make this whatever you want it to be, so long as it is not used anywhere else in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add rewrites
&lt;/h2&gt;

&lt;p&gt;Now we need to tell Next to handle the rewrites for the URL we just provided to Mixpanel.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;next.config.js&lt;/code&gt; add the following (yours might have additional configs already, the rewrites is what we care about).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** @type {import('next').NextConfig} */&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;rewrites&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/mp/lib.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/mp/lib.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.mxpnl.com/libs/mixpanel-2-latest.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/mp/decide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://decide.mixpanel.com/decide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/mp/:slug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// use "api-eu.mixpanel.com" if you need to use EU servers&lt;/span&gt;
        &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.mixpanel.com/:slug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Next, when a request is made to each of these endpoints, it will perform a rewrite to the &lt;code&gt;destination&lt;/code&gt; URL. You can find more details about these rewrites in &lt;a href="https://nextjs.org/docs/api-reference/next.config.js/rewrites"&gt;Next's documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Track things
&lt;/h2&gt;

&lt;p&gt;Now you can use your Mixpanel object throughout your Next project. Import it using &lt;code&gt;import { Mixpanel } from './mixpanel';&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then use it to track events, link clicks, and anything else useful. (These are just examples, not indicative of useful events to track).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Mixpanel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./path/to/mixpanel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PageName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Loaded PageName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;Mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;track_links&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#nav a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nav link clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleButtonClick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button clicked!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"nav"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/dashboard"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Home&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/about"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;About&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/pricing"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Pricing&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleButtonClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Click me!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;PageName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;There you have it. All requests for Mixpanel should now be going through your app's domain, effectively working as a proxy using Next's built-in Rewrites capabilities.&lt;/p&gt;

&lt;p&gt;Let me know if this worked for you, or if you have suggestions for a better approach in the comments!&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>javascript</category>
      <category>mixpanel</category>
    </item>
    <item>
      <title>Building a headless eCommerce shop with Magento and Vue</title>
      <dc:creator>Joel Rainwater</dc:creator>
      <pubDate>Fri, 28 Jan 2022 20:14:52 +0000</pubDate>
      <link>https://forem.com/rain2o/building-a-headless-ecommerce-shop-with-magento-and-vue-g7</link>
      <guid>https://forem.com/rain2o/building-a-headless-ecommerce-shop-with-magento-and-vue-g7</guid>
      <description>&lt;h2&gt;
  
  
  How it started
&lt;/h2&gt;

&lt;p&gt;Having worked in Magento for years, I am all too familiar with the challenges that come with using it, especially when it comes to performance. However, I do enjoy the extensibility of Magento, and the massive community that comes with it.&lt;/p&gt;

&lt;p&gt;For years, &lt;a href="https://netatmo.com"&gt;Netatmo's&lt;/a&gt; online &lt;a href="https://shop.netatmo.com"&gt;shop&lt;/a&gt; was running on Magento 1, and it "worked" sufficiently for the time. Between the years of technical debt as a result of numerous developers and agencies working on it, and the upcoming EOL for Magento 1 support, it was quickly becoming obvious that an upgrade was due for the shop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deciding on the stack
&lt;/h2&gt;

&lt;p&gt;One of the most challenging parts of any project - what technology stack do we want to use!?&lt;/p&gt;

&lt;p&gt;We decided to stick with Magento, moving to v2, for a number of reasons. The migration from M1 to M2 should be easier than from Magento to some other platform. I have worked with Magento 1 and 2 for a number of years, and migrated multiple sites from M1 &amp;gt; M2. Being the technical lead of the shop, it made sense for us to use Magento 2 as it would mean less learning time on my part, and an easier onboarding process for other devs. And finally, we couldn't find another eCommerce solution that provided the extensibility of Magento, and allowed us to host it internally due to our very serious approach to security.&lt;/p&gt;

&lt;p&gt;Ok, Magento 2 it is. But, what about the frontend? Magento is notoriously slow, and seems to be getting worse in v2. So what about separating the frontend? There are plenty of choices with all of the powerful JavaScript frameworks and libraries available today.&lt;/p&gt;

&lt;p&gt;Magento PWA Studio was in development at the time, and not ready to use in production. Additionally, it is built in React, and the preferred technology for most of our frontend team is Vue. So, we shopped around.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Enter &lt;a href="https://www.vuestorefront.io/"&gt;Vue Storefront&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With Vue Storefront (VSF), we could build a completely separate frontend as a PWA, using tech like ElasticSearch, Redis, and localStorage to provide a significant boost in performance. This meant a number of important things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limiting communication with Magento&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because most of our data would be stored in ElasticSearch, we could limit API calls to Magento to only necessary moments - when we need live data (checking current stock on checkout) or when an action needs to be stored or calculated by Magento (i.e. placing an order). This means even if Magento is slower than we'd like, it only impacts the user's experience at specific points, which we can control. Of course we still took necessary measures to optimize Magento's performance as best as we could for those moments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Locking down Magento&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The client never talks directly to Magento, but instead goes through the Vue Storefront API. This means, we can completely lock down our Magento instance from the outside world, only allowing communication to and from specific sources which we meticulously control. Of course we still need to follow security best-practices and keep everything up-to-date, but this low-level restriction provides an additional level of protection from inevitable vulnerabilities in a PHP-based framework like Magento.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend freedom&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We would no longer be locked-in to the Magento frontend. This is great because we can share the frontend development across our team without the need to teach others an entirely new templating framework. If you've worked in a Magento theme, you know this is huge - it's not exactly a simple theming system. This meant our Vue developers come be onboarded fairly painlessly, and we could focus on what's important - building it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Improving deployments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Separation of concerns means easier iteration, and less downtime. With Magento being used as headless, our frontend and backend can have separate projects with their own repos, pipelines, etc... This means we can deploy back-end and frontend changes independently without impacting the full stack.&lt;/p&gt;

&lt;p&gt;Also, since the frontend is a PWA, when we do deploy to Magento the frontend won't be completely down. Magento has to run a lot of things during deployment, and only some of it can be handled in the pipeline before taking it down for Maintenance. With VSF, we can allow the frontend to run almost as usual - except for those moments we need to communicate with Magento, which can either be queued or handled as if the user is offline. And because deploying a Vue app is much faster than a full Magento deployment, we can iterate the frontend quickly with virtually no downtime for the users, all without impacting the Magento administration teams.&lt;/p&gt;

&lt;p&gt;Alright, so it's decided. Vue Storefront + Magento 2 will be our stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical overview
&lt;/h2&gt;

&lt;p&gt;I'm not going to give away too much for the sake of security, but here are some details about how Vue Storefront works in general and how we implemented it specifically.&lt;/p&gt;

&lt;p&gt;Let's work our way from back to front.&lt;/p&gt;

&lt;h3&gt;
  
  
  Magento
&lt;/h3&gt;

&lt;p&gt;This layer doesn't need too much detail. It's a pretty standard Magento 2 setup for the most part. The key difference is that our Magento instance is not publicly accessibly. Here are a few benefits and hurdles that this introduced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better access control&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have two ways to access Magento - the API is access at a specific domain, while the back-office is accessed from a different domain. This allows us to maintain a separate list of allowed sources for the API integrations vs administrators. This is on top of the access-control list (ACL) management provided by Magento. Now we've got three layers of managed permissions to get through - Firewall, web server allowances managed per route, and Magento ACL (for both administrators and API users).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend integrations are challenging&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Due to these restrictions, we have some potential hurdles to overcome with any new integration we introduce into Magento.&lt;/p&gt;

&lt;p&gt;The 3rd party's system must provide a list of static IPs, or a way for us to generate a list from their IP pool. Not all systems can provide this which has become a blocking point on a few features. At this point we must either find a new vendor, request the vendor provide us an alternative, or compromise on some workaround between the two of us. It can be frustrating and time-consuming, but in the end I still believe it's worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Magento URLs are always wrong&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Magento comes with some built-in code for generating URLs for products, categories, pages, etc... However, these are inherently wrong for us now because it uses the Magento URL, and not our frontend. This can be a concern with &lt;a href="https://dev.to/rain2o/headless-magento-using-frontend-urls-in-emails-5gd"&gt;emails sent from Magento&lt;/a&gt; for example, or with any 3rd party module that might export data. This isn't a concern on the frontend as Vue Storefront handles this, but when it comes to Magento-only functionality / integrations, this can be an issue.&lt;/p&gt;

&lt;p&gt;For now, the only solution I have been able to come up with is using well-placed plugins to generate frontend URLs when a frontend URL is trying to be generated by Magento. To do this I added some configuration fields in Magento to provide things like the frontend base URL, and some path details. We also pull some final data from ElasticSearch (more on that later) to use the same indexed data as VSF.&lt;/p&gt;

&lt;p&gt;I have hopes that Magento might introduce a similar type of feature one day, especially with the new PWA Studio they provide and the increasing popularity of the headless approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  ElasticSearch
&lt;/h3&gt;

&lt;p&gt;The team at Vue Storefront provides a &lt;a href="https://github.com/vuestorefront/magento2-vsbridge-indexer"&gt;Magento 2 module&lt;/a&gt; which uses Magento's native indexing functionality to index all pertinent data to ElasticSearch (ES) in a format which can be utilized by VSF. Because it hooks into the native indexing of M2, data can be automatically reindexed on Save if you wish to configure it to do so.&lt;/p&gt;

&lt;p&gt;Now, I know Magento 2 also comes with ES, and they can use the same instance I believe, but VSF requires the data to be sent in a uniform way it can read. They have a "platform agnostic" approach, so the data on the front needs to be as &lt;em&gt;pure&lt;/em&gt; as possible, removing back-end platform flavor where possible. The module seems well-built, and even has a CLI command for reindexing manually with a few options. We have also created a few of our own modules which extend the VsBridge module to index some custom data points to ES.&lt;/p&gt;

&lt;p&gt;Thanks to this module, the static data now sits in ES, allowing our frontend to quickly fetch minimal data as needed without putting load on Magento.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vue Storefront API
&lt;/h3&gt;

&lt;p&gt;Vue Storefront comes with &lt;a href="https://github.com/vuestorefront/vue-storefront-api"&gt;its own API&lt;/a&gt; which serves as a sort of middleware. It is written in NodeJS, which is nice because it means another technology isn't introduced into our stack. JavaScript developers can work on this layer as needed and feel right at home.&lt;/p&gt;

&lt;p&gt;This API layer is what the frontend uses for all of its requests. The API handles the decision of where and how to send or retrieve data. It also handles the platform-specific communication - this is the pivot point for the platform agnostic approach. It can decide if it should check Redis (cache) for data being requested, or look in ElasticSearch, request directly from Magento 2 (or whatever your backend platform might be), or any other 3rd party integration you might decide to introduce.&lt;/p&gt;

&lt;p&gt;Because it is built in Node, this means you can handle API integrations without exposing keys and such to the client side. It also lets the frontend not worry about system logic, and focus on being a great frontend.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt; - The VSF team is working on a &lt;a href="https://github.com/vuestorefront/storefront-api"&gt;new Storefront API&lt;/a&gt; which is being built in a more modular and agnostic way. I believe their intention is for it to be able to be used with any stack, not tied to VSF on the front. I haven't used it yet as it's still not production-ready last time I checked, but I'm excited to try it out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vue Storefront
&lt;/h3&gt;

&lt;p&gt;Finally, the part everyone sees - the blazing fast Vue frontend!&lt;/p&gt;

&lt;p&gt;Vue Storefront uses Vuex under the hood for state management, and has separated the stores in a modular fashion - similar to Magento's modular approach of extensions in v2. It also has example modules for a great starting point in building your own modules. You can easily include/exclude any modules you wish, because not all shops are alike.&lt;/p&gt;

&lt;p&gt;For example, I recently built our own Newsletter module and excluded the built-in Newsletter module. This was because we had so many differences in how we wanted to handle it that it just made more sense than trying to extend the core module to work for us.&lt;/p&gt;

&lt;p&gt;The framework is extensible as well, and getting better every day. In addition to standard Vue features like &lt;code&gt;subscribe&lt;/code&gt; and &lt;code&gt;subscribeAction&lt;/code&gt;, VSF has introduced &lt;code&gt;hooks&lt;/code&gt; in key places throughout the core. You can fairly painlessly subscribe to a particular hook to either trigger a reaction to a specific event, or in some cases mutate the data being used after the hook. Bus Events are also used throughout core as another way to easily hook into events to trigger your own behavior at key points.&lt;/p&gt;

&lt;p&gt;There is a basic theme which comes with Vue Storefront, though in recent versions they have been pushing to use &lt;a href="https://capybara.storefrontcloud.io/"&gt;Capybara&lt;/a&gt; as a starting point, or reference point, for your theme building. Of course this is up to you, but it's nice to have a reference when building a theme in a framework like this.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt; - If you look at Vue Storefront, you will see &lt;strong&gt;v1&lt;/strong&gt; and &lt;strong&gt;V2&lt;/strong&gt;. At the time of building our shop VSF 2 was not ready for Magento. It is a new build of Vue Storefront, but they are focusing on other back-end integrations as the v1 has a solid integration with M2. Just something worth noting. VSF 2 is built on Nuxt, which is exciting as well!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;If you made it this far, thank you for sticking with me! Just a few closing thoughts.&lt;/p&gt;

&lt;p&gt;Looking at the architecture, it does seem quite complex compared to a standard Magento setup, but in the end it is totally worth it. Plus, with the separation of technologies, you can separate responsibilities more easily, and it becomes less overwhelming.&lt;/p&gt;

&lt;p&gt;One thing that I really enjoy about this setup is that everything is Open Source. Magento has always been open source, and has a massive community. Vue Storefront is open source, along with all of its tools. Their community is rapidly growing, and very active. During our project I was able to contribute to various VSF projects multiple times, as well as participate in community conversations on their Slack/Discord. I truly enjoy working in technologies with this sort of environment, plus it's a nice feeling to know you contributed to a core piece of technology you use day-to-day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions/Comments?
&lt;/h2&gt;

&lt;p&gt;There is so much I didn't get to talk about in this already too long post. If there is something you would like to hear more about, leave a comment and let me know! If it's something I can disclose I'd be happy to write another post about more specific pieces.&lt;/p&gt;

&lt;p&gt;Let me know what technology you used for your eCommerce site, I'd love to see what I'm missing out on!&lt;/p&gt;

</description>
      <category>magento</category>
      <category>vue</category>
      <category>javascript</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>I built my first SaaS on Next.js</title>
      <dc:creator>Joel Rainwater</dc:creator>
      <pubDate>Tue, 17 Aug 2021 18:59:59 +0000</pubDate>
      <link>https://forem.com/rain2o/i-built-my-first-saas-on-next-js-21o</link>
      <guid>https://forem.com/rain2o/i-built-my-first-saas-on-next-js-21o</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I have been a software engineer in some form or fashion for around 10 years, working in anything from Python, PHP, Swift, C# to vanilla JS, jQuery, Vue, and React. Over the past few years I have been getting to know the modern JS framework better, playing around with the different frameworks. I spend most of my days working in Vue, and I have created some side-projects in React to have a better understanding of them both. &lt;/p&gt;

&lt;p&gt;I'm not a fan of these tech wars, and to me these JS frameworks are ultimately the same thing. I have enjoyed working in Vue a bit more, but it's just due to personal preference.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;A few years ago I had to take a car to the mechanic. After a week of not hearing anything I called them. Apparently they needed to replace the engine (it was a known issue with this make/model). This wait... call... wait... call... cycle happened for weeks until the job was complete. I hated it.&lt;/p&gt;

&lt;p&gt;So, I had the idea that it could be useful for service-providers to have an easy-to-use platform to provide these status updates to their customers which could notify the customer via email, sms, or push notifications. It could provide a timeline view of the job with the history of updates, etc... And if possible, would be great to integrate into existing tools as to not create too much additional work on their part. But it could same them from repeated customer calls asking the same question - "What's the status of my ____?"&lt;/p&gt;

&lt;p&gt;But I didn't do anything about it. Long story short, it took me around 4 years to finally get around to building the thing.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://statustracker.app" rel="noopener noreferrer"&gt;Status Tracker&lt;/a&gt;. At the time of writing this it is still in private Early Access. There is a sign-up form on the landing page for those interested in getting early access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Next.js
&lt;/h2&gt;

&lt;p&gt;As I mentioned in my background, I usually prefer Vue, I just enjoy it more, but I'm open to using other technologies for the right reasons. I was planning to build it in Nuxt.js because I haven't used it much but I've heard great things.&lt;/p&gt;

&lt;p&gt;And then &lt;a href="https://twitter.com/mxstbr" rel="noopener noreferrer"&gt;Max Stoiber&lt;/a&gt; released &lt;a href="https://bedrock.mxstbr.com/" rel="noopener noreferrer"&gt;Bedrock&lt;/a&gt;. It really had everything already setup out of the box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.graphql-code-generator.com/" rel="noopener noreferrer"&gt;GraphQL Codegen&lt;/a&gt; and &lt;a href="https://nexusjs.org/" rel="noopener noreferrer"&gt;Nexus&lt;/a&gt; meant full-stack typing from DB models to client queries and components.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.passportjs.org/" rel="noopener noreferrer"&gt;Passport&lt;/a&gt; - User auth already setup and ready to go&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stripe.com/" rel="noopener noreferrer"&gt;Stripe&lt;/a&gt; - Basic Stripe integration for subscription plans configured and working.&lt;/li&gt;
&lt;li&gt;Things like &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt; and &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; already configured to lint and auto-fix issues while coding and at pre-commit.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://postmarkapp.com/" rel="noopener noreferrer"&gt;Postmark&lt;/a&gt; for transactional emails&lt;/li&gt;
&lt;li&gt;A few other things...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, it was exactly what I needed for my first time building a SaaS - a boilerplate with the essentials already working. It was ready for me to start adding my custom features to it as soon as I got it. Not to mention Max created a Discord server for those who have purchased a license of Bedrock so that we can support each other and share our projects.&lt;/p&gt;

&lt;p&gt;I pre-ordered it before it was released and got it at a great deal. But even at full price it's more than worth it.&lt;/p&gt;

&lt;p&gt;So, this setup was definitely worth the switch from Vue to React. It gave me a chance to learn even more about React as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get technical
&lt;/h2&gt;

&lt;p&gt;I'll try not to go too deep into details here, but if you are curious to find out more about anything in particular let me know and I can write up a smaller post about it! I'm also not going to drone on about all of the models and views that I created. I just want to cover some of the more fun, interesting, or difficult things I worked on.&lt;/p&gt;

&lt;p&gt;Here's what we'll go over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hosting&lt;/li&gt;
&lt;li&gt;Frontend component library&lt;/li&gt;
&lt;li&gt;Email notifications&lt;/li&gt;
&lt;li&gt;SMS notifications&lt;/li&gt;
&lt;li&gt;URL Shortener&lt;/li&gt;
&lt;li&gt;Scheduled jobs&lt;/li&gt;
&lt;li&gt;Customer portal with custom branding&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Hosting
&lt;/h3&gt;

&lt;p&gt;I decided to try Vercel for hosting this project. I have used Netlify in the past, but since Vercel was built by the same people who built Next.js, and it was originally created for Next.js, I thought it would be a great fit. It was simple to setup and works pretty well. A few caveats I had to work around with my specific setup, but they typically had documentation around this.&lt;/p&gt;

&lt;p&gt;For hosting the database (which is Postgres) I went with &lt;a href="https://supabase.io/" rel="noopener noreferrer"&gt;supabase&lt;/a&gt;. I was already using &lt;a href="https://www.prisma.io/" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt; for the ORM since it came with Bedrock, so I didn't use the SDJ from Supabase. However, it was simple to setup a new account, grab the connection string, and hook it into my existing code. They have a good free tier for a project like this. And I enjoy their UI when using the web app.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Frontend component library
&lt;/h3&gt;

&lt;p&gt;I decided to go with &lt;a href="https://material-ui.com/" rel="noopener noreferrer"&gt;Material-UI&lt;/a&gt; for a number of reasons.&lt;br&gt;
For starters, I'm not very strong in design, so I wanted an opinionated frontend framework that would guide me to better design. Something that didn't require very much customization to make it look decent. I have heard great things about Material-UI, and it works great with Next.js. They also provide some nice demos to help inspire my non-creative brain.&lt;/p&gt;

&lt;p&gt;I decided on a basic color palette and created a custom theme using their theme config. After that it was mostly import and use, with a few adjustments for spacing and such. It's not beautiful, but my goal was to make it easy to use and understand. I'll worrying about a makeover later.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Email notifications
&lt;/h3&gt;

&lt;p&gt;For sending transactional emails I stuck with Postmark JS as the boilerplate came with it baked in. I could have switched it out fairly easily, but decided to stick with what was ready for me.&lt;/p&gt;

&lt;p&gt;I setup some templates in Postmark using their pre-made templates as a starting point. I could define variables in their template (like a user's name or a company/project name). Then using their SDK it was pretty simple to send emails with the intended template and variables. Here's an abbreviated example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postmark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendEmailWithTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;From&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FROM_EMAIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;To&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;TemplateAlias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;TemplateModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;company&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;action_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Postmark was pretty easy to setup and use, and their default templates are better than what I would have created. So far I am pretty happy with them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; SMS notifications
&lt;/h3&gt;

&lt;p&gt;Originally I did not plan to have SMS notifications. However, I had some conversations with potential customers, and it was brought to my attention that the intended users might be on their phones in the field. For example, a construction crew on the project site. I decided to go ahead and add this, even though it sort of stretched the MVP concept.&lt;/p&gt;

&lt;p&gt;For the frontend component, I decided to use &lt;a href="https://github.com/alexplumb/material-ui-phone-number" rel="noopener noreferrer"&gt;Material-UI Phone Number&lt;/a&gt;, which is a fork of &lt;code&gt;react-phone-input-2&lt;/code&gt; made for Material-UI. I didn't want to spend time building my own phone number input, but having the auto-format and country select was a nice feature.&lt;/p&gt;

&lt;p&gt;I also decided to use &lt;a href="https://www.npmjs.com/package/google-libphonenumber" rel="noopener noreferrer"&gt;google-libphonenumber&lt;/a&gt; on the server-side to handle formatting and validation of the phone input.&lt;/p&gt;

&lt;p&gt;To send the SMS notifications I went with &lt;a href="https://aws.amazon.com/sns/" rel="noopener noreferrer"&gt;Amazon SNS&lt;/a&gt;. I was hesitant to do this because every time I have worked with anything AWS related I have wasted hours trying to figure out how to get everything configured correctly. Fortunately SNS wasn't too complicated, and their docs helped me through it. One great benefit is that you get something like 1 million requests free on the AWS Free Tier. This should be plenty to get me started.&lt;/p&gt;

&lt;p&gt;Here's a truncated version of how I'm using SNS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PublishCommand&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PublishCommandInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-sdk/client-sns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PhoneNumberFormat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PhoneNumberUtil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google-libphonenumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;SendSMSInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SNSClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Create instance of phone util for formatting&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;phoneUtil&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PhoneNumberUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sendSMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SendSMSInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// make sure phone is E164 format&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;phoneUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PublishCommandInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;phoneUtil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PhoneNumberFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;E164&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PublishCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; URL Shortener
&lt;/h3&gt;

&lt;p&gt;Once I setup SMS notifications I realized that I would need a URL shortener. Since I'm using the Magic Link authentication process, the URLs can be quite long. In an SMS, this is terrible to see.&lt;/p&gt;

&lt;p&gt;After some research, I decided to give &lt;a href="https://kutt.it/" rel="noopener noreferrer"&gt;Kutt&lt;/a&gt; a try. It's open source and free, and there is a node SDK which can make it easy to use. &lt;/p&gt;

&lt;p&gt;It was simple to setup and use. Here's a shortened version of how I use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Kutt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kutt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getShortUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kutt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Kutt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;kutt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;kutt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shortUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Schedule jobs
&lt;/h3&gt;

&lt;p&gt;Since I decided to host on Vercel, I don't really have a server to run crons on. The backend is all serverless functions. But, one feature that Status Tracker provides is the ability to set a reminder for yourself. For example, when you create a new job, you want to remind yourself 1 hour after the job is scheduled to update the status.&lt;/p&gt;

&lt;p&gt;In order to create these scheduled reminders, I needed to be able to have a cron or some sort of scheduled job. I thought about going with another AWS service for this, but once again I'm always hesitant about this.&lt;/p&gt;

&lt;p&gt;Then I found &lt;a href="https://quirrel.dev/" rel="noopener noreferrer"&gt;Quirrel&lt;/a&gt;. Their whole thing is "Quirrel makes job queues simple as cake." This is what I was looking for.&lt;/p&gt;

&lt;p&gt;The docs looked simple and straight forward. The technology is open-source. The pricing plans are good for a small Saas like mine. I decided to go for it.&lt;/p&gt;

&lt;p&gt;Basically I created an API endpoint to create a new queued job. This endpoint tells Quirrel when to execute the job, and an API endpoint to call at that time. Then I have another endpoint which receives the call from Quirrel at the scheduled time, and it sends the notifications using the referenced data.&lt;/p&gt;

&lt;p&gt;The slogan was pretty spot-on. It was simple to setup, the docs walked me through it, and I got it up and running quickly. So far I have been happy with the service as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a&gt;&lt;/a&gt; Customer portal
&lt;/h3&gt;

&lt;p&gt;This was the fun part. I wanted to create a custom sub-domain for each organization who uses Status Tracker. I also wanted to give them the ability to use their own branding instead of ours, similar to how Stripe does with their checkout portal.&lt;/p&gt;

&lt;p&gt;When a user creates a new organization, they provide the name, and we auto-generate a subdomain. However they are given the choice to change the subdomain if they don't like what we created.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foue5bsey17i7alvq3dum.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foue5bsey17i7alvq3dum.png" alt="A form showing an input for Company Name and URL Slug."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the company is created they can configure their branding if they choose under their company settings.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc79dac9gen5246vr2dlt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc79dac9gen5246vr2dlt.png" alt="Screenshot showing two color picker fields for Primary and Secondary colors, and a photo upload field for Logo."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There were two major things I had to figure out at this point.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How could I auto-create the company's subdomain so that I don't have to manually do this each time?&lt;/li&gt;
&lt;li&gt;How could I build the customer portal using a dynamic theme which comes from database values?&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Creating the subdomains
&lt;/h4&gt;

&lt;p&gt;I didn't want to deal with setting up (even if automated) Nginx configs and SSL certs every time a new company registered. I decided to try using a wildcard subdomain which points to a general customer portal.&lt;/p&gt;

&lt;p&gt;I'm using Vercel to host the app, so I pointed my domain servers to Vercle and configured the DNS to use &lt;code&gt;*.statustracker.app&lt;/code&gt; for this secondary app. Vercel took care of the SSL cert.&lt;/p&gt;

&lt;p&gt;In the app itself (another Next.js client-only app), I created the same &lt;code&gt;theme.ts&lt;/code&gt; file with the standard Status Tracker branding as the default theme. I already had a custom &lt;code&gt;_app.tsx&lt;/code&gt; file to handle some other things, so in there I added a custom &lt;code&gt;getInitialProps&lt;/code&gt; function.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE - I have discovered since building this that perhaps I should be using &lt;code&gt;getServerSideProps&lt;/code&gt; instead. I will be testing this change soon.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this function I call the API to retrieve the company using the slug from the subdomain. If none is found I redirect to the main Status Tracker site. If it does find a company, it returns the theme settings to the component.&lt;/p&gt;

&lt;h4&gt;
  
  
  Custom branding
&lt;/h4&gt;

&lt;p&gt;In the component where I create the theme provider, I check for any custom theme settings provided by the app props before creating the theme provider, using the default theme as a fallback.&lt;/p&gt;

&lt;p&gt;Here is a sample of the difference in branding on the login page.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vpyg7o0qwv4rrgljdwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vpyg7o0qwv4rrgljdwi.png" alt="Two screenshots of the customer portal showing a logo and a simple login form. One screenshot uses the Status Tracker logo and colors - purple and green-ish. The other uses a logo for Acme, Inc. and their brand colors - red and black."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That's a wrap
&lt;/h2&gt;

&lt;p&gt;That is everything interesting I could think to write about my experience building the app. I plan to write more about the project as it progresses. You can follow &lt;a href="https://dev.to/rain2o"&gt;me on dev.to&lt;/a&gt;, or follow along the milestones of Status Tracker on &lt;a href="https://www.indiehackers.com/product/status-tracker" rel="noopener noreferrer"&gt;the Indie Hackers product page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any questions, or want to know more about something in particular leave a comment and let me know!&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>saas</category>
    </item>
    <item>
      <title>Headless Magento - Using Frontend URLs in Emails</title>
      <dc:creator>Joel Rainwater</dc:creator>
      <pubDate>Fri, 26 Mar 2021 15:56:53 +0000</pubDate>
      <link>https://forem.com/rain2o/headless-magento-using-frontend-urls-in-emails-5gd</link>
      <guid>https://forem.com/rain2o/headless-magento-using-frontend-urls-in-emails-5gd</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;Original article: &lt;a href="https://blog.rainwater.io/2021/03/26/headless-magento-using-frontend-urls-in-emails" rel="noopener noreferrer"&gt;https://blog.rainwater.io/2021/03/26/headless-magento-using-frontend-urls-in-emails&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Overview
&lt;/h2&gt;

&lt;p&gt;Magento sends a lot of emails, and most of these emails have pertinent information like the list of products purchased or being shipped, order details, etc... Usually these emails provide a number of links to Magento so that the customer can view their account, order, a product, and so on.&lt;/p&gt;

&lt;p&gt;But what can we do if we are using Magento as headless? It will send the customer the Magento URL instead of our frontend URL.&lt;/p&gt;

&lt;p&gt;This is the solution I came up with. It might not be the &lt;em&gt;best&lt;/em&gt;, but it's working for us.&lt;/p&gt;

&lt;p&gt;If you would like to see the final code, I created &lt;a href="https://github.com/rain2o/module-frontend" rel="noopener noreferrer"&gt;a sample module&lt;/a&gt; to go with this article.&lt;/p&gt;




&lt;h2&gt;
  
  
  Create a new module
&lt;/h2&gt;

&lt;p&gt;Following Magento's modular approach, let's create a new module to contain this functionality. Throughout this article I'll be using &lt;code&gt;Rain2o_Frontend&lt;/code&gt; as the module name in the examples. Remember to replace these with your own module name when following along.&lt;/p&gt;

&lt;p&gt;I won't cover how to create a module, this is covered in great detail by many articles as well as the &lt;a href="https://devdocs.magento.com/videos/fundamentals/create-a-new-module/" rel="noopener noreferrer"&gt;Magento documentation&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Add some configurations
&lt;/h2&gt;

&lt;p&gt;This step might not be necessary for your setup, but I like to keep environment details, like a URL for example, flexible instead of coded so it can be changed per environment easily. To do this, I added a new field in the Stores Configuration section to manage the Frontend URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the new field
&lt;/h3&gt;

&lt;p&gt;Create a new file if you haven't already at &lt;code&gt;app/code/Rain2o/Frontend/etc/adminhtml/system.xml&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;I decided to add a new group entirely for the Frontend URL field. This is because our setup actually contains multiple fields in this group, but that's just a unique need for our project. Having this separate group allows us to have a separate section that's easy to find and ready to grow as additional requirements are introduced.&lt;/p&gt;

&lt;p&gt;This is what I have in &lt;code&gt;system.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/code/Rain2o/Frontend/etc/adminhtml/system.xml --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:module:Magento_Config:etc/system_file.xs"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;system&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"web"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;group&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"frontend"&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"label comment"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;sortOrder=&lt;/span&gt;&lt;span class="s"&gt;"25"&lt;/span&gt; &lt;span class="na"&gt;showInDefault=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;showInWebsite=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;showInStore=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Frontend URLs&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"base_url"&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"label comment"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;sortOrder=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;showInDefault=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;showInWebsite=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;showInStore=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Frontend Base URL&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;comment&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[Specify full URL for frontend.]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/comment&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/system&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is fairly straight-forward if you're familiar with Magento's system.xml file, but I'll break it down a little.&lt;/p&gt;

&lt;p&gt;We are adding one new field called &lt;code&gt;base_url&lt;/code&gt; inside a new group we created called &lt;code&gt;frontend&lt;/code&gt;. This is all inside Magento's existing section &lt;code&gt;web&lt;/code&gt;, which can be found in the admin at Stores -&amp;gt; Configuration -&amp;gt; General -&amp;gt; Web.&lt;/p&gt;

&lt;p&gt;This field is set to be editable in all scopes - Global, Website, and Store. This is up to you and your needs. Just note that I handle multi-store functionality later, so there is no need to set this per store here. But you can do so if that fits your needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting default value
&lt;/h3&gt;

&lt;p&gt;Let's set a default value too, just so there's something to start with.&lt;/p&gt;

&lt;p&gt;Create the file &lt;code&gt;app/code/Rain2o/Frontend/etc/config.xml&lt;/code&gt;. Here I set the &lt;em&gt;production&lt;/em&gt; URL as the default. This can be changed in Magento per environment, but this guarantees production will start with the right value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/code/Rain2o/Frontend/etc/config.xml --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:module:Magento_Store:etc/config.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;default&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;web&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;frontend&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;base_url&amp;gt;&lt;/span&gt;https://shop.example.com/&lt;span class="nt"&gt;&amp;lt;/base_url&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/frontend&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/web&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/default&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, install the module if you haven't already:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/magento setup:upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if the module is already installed, clean the cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/magento cache:clean
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you do that, you should see this in the &lt;em&gt;Web&lt;/em&gt; configuration section:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7emjhje619kqfdxxtad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7emjhje619kqfdxxtad.png" alt="The new Frontend URL field displayed in Magento's Configuration page, under the General Web section."&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  How are URLs created in Emails?
&lt;/h2&gt;

&lt;p&gt;Alright, now that the groundwork is done, let's figure out how to override the URLs generated in emails.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Feel free to skip this part. I prefer to understand what I'm changing and why it is done in a certain way, so I wanted to help provide these details. If you just want to do the work, go to the next step.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After some digging, I discovered Magento's email templates use the model &lt;code&gt;\Magento\Email\Model\Template&lt;/code&gt; for building the emails, which extends &lt;code&gt;\Magento\Email\Model\AbstractTemplate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;AbstractTemplate&lt;/code&gt; you will see the function &lt;code&gt;getUrl&lt;/code&gt;, which is what is used in the email templates. This uses the &lt;em&gt;private&lt;/em&gt; property &lt;code&gt;$urlModel&lt;/code&gt;, which is passed to the &lt;code&gt;__construct&lt;/code&gt; as a dependency. The model that is used by default is &lt;code&gt;\Magento\Framework\Url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Digging around in &lt;code&gt;\Magento\Framework\Url&lt;/code&gt;, we can see that there are two main functions that could be useful in generating the correct frontend URLs - &lt;code&gt;getRouteUrl&lt;/code&gt; and &lt;code&gt;getBaseUrl&lt;/code&gt;. Both of these functions are ultimately called from the &lt;code&gt;getUrl&lt;/code&gt; function which is initially called in the template. &lt;code&gt;getBaseUrl&lt;/code&gt; is where we can use the new field we just created for the base. But our frontend might not follow the same routing structure as Magento, so &lt;code&gt;getRouteUrl&lt;/code&gt; is where we can handle those route changes.&lt;/p&gt;

&lt;p&gt;But how do we do that without hacking core? Both of those functions are &lt;code&gt;public&lt;/code&gt;, so we could use &lt;a href="https://devdocs.magento.com/guides/v2.4/extension-dev-guide/plugins.html" rel="noopener noreferrer"&gt;Plugins&lt;/a&gt;. But I chose instead to use dependency inject to inject a new URL model for email templates. This avoids having multiple plugins on a model which is probably used a lot throughout Magento, and instead gives us a single Model to handle all frontend URL logic. We can then use this model later as we discover new parts of Magento that might need this functionality.&lt;/p&gt;


&lt;h2&gt;
  
  
  Overriding email URLs
&lt;/h2&gt;

&lt;p&gt;Let's use &lt;a href="https://devdocs.magento.com/guides/v2.4/extension-dev-guide/build/di-xml-file.html" rel="noopener noreferrer"&gt;Magento's Dependency Inject file&lt;/a&gt; to change the model which is passed to the email template for &lt;code&gt;urlModel&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Inject our own URL model
&lt;/h3&gt;

&lt;p&gt;Create the file &lt;code&gt;app/code/Rain2o/Frontend/etc/di.xml&lt;/code&gt;. Here we will tell Magento to use our own URL Model in the &lt;code&gt;__construct&lt;/code&gt; of &lt;code&gt;\Magento\Email\Model\Template&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- app/code/Rain2o/Frontend/etc/di.xml --&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;config&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:framework:ObjectManager/etc/config.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;type&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Magento\Email\Model\Template"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;arguments&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"urlModel"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"object"&lt;/span&gt; &lt;span class="na"&gt;shared=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Rain2o\Frontend\Model\Url&lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/arguments&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/config&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We told Magento that the &lt;code&gt;urlModel&lt;/code&gt; argument for the class &lt;code&gt;\Magento\Email\Model\Template&lt;/code&gt; should use our own model (we haven't created it yet) instead of the default model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create our new URL Model
&lt;/h3&gt;

&lt;p&gt;Now let's create the model we just referenced. This is where the good stuff will happen.&lt;/p&gt;

&lt;p&gt;Create your model &lt;code&gt;app/code/Rain2o/Frontend/Model/Url.php&lt;/code&gt;. If you look again at the Email Template model, you'll see that &lt;code&gt;urlModel&lt;/code&gt; is passed as &lt;code&gt;\Magento\Framework\UrlInterface $urlModel&lt;/code&gt;. Since we are changing the model for this, we need to be sure our model also implements that interface.&lt;/p&gt;

&lt;p&gt;First let's create the class skeleton, then we'll add in the pieces.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOTE&lt;/em&gt; - I am leaving out DocBlocks and comments to keep the sample code slim. Don't forget to document your code thoroughly!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="cd"&gt;/** app/code/Rain2o/Frontend/Model/Url */&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Rain2o\Frontend\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Framework\UrlInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Url&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Magento\Framework\Url&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;UrlInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see I extended the model we are replacing - &lt;code&gt;Magento\Framework\Url&lt;/code&gt;. This will allow us to use existing functionality without rewriting it, and only replace the pieces we need to modify.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Frontend URL from config
&lt;/h3&gt;

&lt;p&gt;The first thing we know we're going to need to do is get the value of the new field we added in &lt;code&gt;system.xml&lt;/code&gt;. Fortunately, the model we are extending already has a &lt;code&gt;protected&lt;/code&gt; function &lt;code&gt;_getConfig&lt;/code&gt; to get config values.&lt;/p&gt;

&lt;p&gt;At the top of the class, let's add a constant to contain the path to our new field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;FE_URL_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web/frontend/base_url"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also want to make sure that our frontend URL logic is only executed if the current scope is for frontend. So let's go ahead and add a flag to indicate if we're in admin scope or not. We'll use this later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cd"&gt;/**
 * @var bool
 */&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$isAdmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base model is rather large, and this next part can be daunting. So let's start from the bottom and work our way up. The first thing we need to modify is the base url. So let's copy the &lt;code&gt;public function getBaseUrl($params = []) {}&lt;/code&gt; function to our new model from the original in &lt;code&gt;Magento\Framework\Url&lt;/code&gt;. We will add a few pieces into the existing code. Here's the final version of the function, we'll break it down next.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getBaseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cd"&gt;/**
     *  Original Scope
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;origScope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_scope'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_scope'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// CUSTOM CODE&lt;/span&gt;
    &lt;span class="c1"&gt;// we only want to override if we're in frontend&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getScope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isAdmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getBaseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isAdmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// END CUSTOM CODE&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_type'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRouteParamsResolver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;setType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_type'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_secure'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRouteParamsResolver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setSecure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_secure'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Add availability support urls without scope code
     */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;UrlInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;URL_TYPE_LINK&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isDirectAccessFrontendName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getRouteFrontName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRouteParamsResolver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;setType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UrlInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;URL_TYPE_DIRECT_LINK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// CUSTOM CODE&lt;/span&gt;
    &lt;span class="c1"&gt;// remove slash so we can add one and know it's only one&lt;/span&gt;
    &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rtrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FE_URL_PATH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// add store code&lt;/span&gt;
    &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getScope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// END CUSTOM CODE&lt;/span&gt;

    &lt;span class="c1"&gt;// setting back the original scope&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;origScope&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRouteParamsResolver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;setType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_URL_TYPE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I surrounded any additions or changes in comments to make it easier to see what we modified.&lt;/p&gt;

&lt;p&gt;The first thing we did was check the current scope. If it's admin we set our &lt;code&gt;$this-&amp;gt;isAdmin&lt;/code&gt; flag to true (we will check this in other functions later), and then return the execution of the parent function. This way we don't modify the behavior for admin URLs, and we stop any further execution of our custom logic.&lt;/p&gt;

&lt;p&gt;The second change was how we create &lt;code&gt;$result&lt;/code&gt;. Instead of using Magento's built in function (previously it was &lt;code&gt;$this-&amp;gt;_getScope()-&amp;gt;getBaseUrl(...)&lt;/code&gt;, we want to use our new value.&lt;/p&gt;

&lt;p&gt;So we modified it to be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rtrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FE_URL_PATH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;rtrim&lt;/code&gt; is just an extra precaution to ensure there is always one, and only one slash at the end. Since this is a field in Magento configuration, we can't always control that, so we force it this way.&lt;/p&gt;

&lt;p&gt;The next line we add the store code to the URL. Of course this is optional according to your setup. We actually use locales in our frontend URLs, so I have additional logic to convert the store code to the appropriate local, but that's not necessary for this article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getScope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can modify this to fit your store's needs.&lt;/p&gt;

&lt;p&gt;And that's it for that function. We now should be retrieving the frontend base URL with store code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling routes
&lt;/h3&gt;

&lt;p&gt;The other function I mentioned earlier is &lt;code&gt;getRouteUrl&lt;/code&gt;. This is where we need to handle our route patterns for the frontend. The parent model we are extending uses Magento code for generating routes which we want to bypass. The actual logic for that is in a couple of &lt;code&gt;protected&lt;/code&gt; functions we'll look at next. The code for this function is pretty slim.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getRouteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$routeParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// get our new base URL for frontend&lt;/span&gt;
    &lt;span class="nv"&gt;$base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBaseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routeParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// use parent if we're in admin scope&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getRouteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$routeParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// route mapping happens here&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_setRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// use our base url and the mapped route path&lt;/span&gt;
    &lt;span class="nv"&gt;$frontUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$base&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routeParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$frontUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First we get the new base url we just created. Next we check if we're in admin scope, and if so then just execute the parent function again. Otherwise we call &lt;code&gt;$this-&amp;gt;_setRoutePath()&lt;/code&gt;, which is where the actual mapping of routes happens.&lt;/p&gt;

&lt;p&gt;And finally we combine all of the above work to create our full frontend URL with route.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mapping the routes
&lt;/h3&gt;

&lt;p&gt;Now we need to handle the mapping of routes. First we'll create our own &lt;code&gt;_setRoutePath&lt;/code&gt; function to handle this. If you look at the parent class, this function uses a lot of Magento code like &lt;code&gt;$this-&amp;gt;_getRequest()-&amp;gt;getControllerName();&lt;/code&gt; and similar. We don't want any of this for our frontend. This function actually gets a bit smaller, depending on your mapping needs.&lt;/p&gt;

&lt;p&gt;For our setup, we actually have pretty straightforward routes, so we don't have any mapping logic. Instead we just use the route path as-is, because that matches our frontend routes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;_setRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// kept from original&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'route_path'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unsetData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'route_path'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$routePieces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// additional logic here if needed to map route path to frontend routes&lt;/span&gt;
    &lt;span class="nv"&gt;$pieces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;yourFunctionForMappingRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routePieces&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'route_path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pieces&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the code we have is kept from the original function, but we removed a lot of unused code. I placed a comment where you could implement custom logic for mapping routes according to your needs. This will be different for everyone.&lt;/p&gt;

&lt;p&gt;We set the data after handling the mapping, and we're done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the mapped routes
&lt;/h3&gt;

&lt;p&gt;We also need to remove some logic from the &lt;code&gt;_getRoutePath&lt;/code&gt; function, because it uses some additional Magento logic for rewrites that we don't need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;_getRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routeParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// use parent function if we're in admin scope&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_getRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routeParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'route_path'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We first check if we're in admin, and if so execute the parent function again. After that we only return the previously set &lt;code&gt;route_path&lt;/code&gt; data.&lt;/p&gt;

&lt;p&gt;And that does it. Now if you clean the cache - &lt;code&gt;bin/magento cache:clean&lt;/code&gt;, you should be able to test your emails and find the new frontend URLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Look
&lt;/h3&gt;

&lt;p&gt;Here is the final version of our URL model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Rain2o\Frontend\Model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Magento\Framework\UrlInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Url&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\Magento\Framework\Url&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;UrlInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;FE_URL_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web/frontend/base_url"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * @var bool
     */&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$isAdmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getBaseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cd"&gt;/**
         *  Original Scope
         */&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;origScope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_scope'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_scope'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// CUSTOM CODE&lt;/span&gt;
        &lt;span class="c1"&gt;// we only want to override if we're in frontend&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getScope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isAdmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getBaseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isAdmin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// END CUSTOM CODE&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_type'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRouteParamsResolver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;setType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_type'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_secure'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRouteParamsResolver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setSecure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_secure'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="cd"&gt;/**
         * Add availability support urls without scope code
         */&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;UrlInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;URL_TYPE_LINK&lt;/span&gt;
            &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getRequest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isDirectAccessFrontendName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getRouteFrontName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRouteParamsResolver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;setType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UrlInterface&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;URL_TYPE_DIRECT_LINK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// CUSTOM CODE&lt;/span&gt;
        &lt;span class="c1"&gt;// remove slash so we can add one and know it's only one&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;rtrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FE_URL_PATH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// add store code&lt;/span&gt;
        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getScope&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// END CUSTOM CODE&lt;/span&gt;

        &lt;span class="c1"&gt;// setting back the original scope&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;origScope&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getRouteParamsResolver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;setType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DEFAULT_URL_TYPE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getRouteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$routeParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// get our new base URL for frontend&lt;/span&gt;
        &lt;span class="nv"&gt;$base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBaseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routeParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// use parent if we're in admin scope&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getRouteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$routeParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// route mapping happens here&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_setRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// use our base url and the mapped route path&lt;/span&gt;
        &lt;span class="nv"&gt;$frontUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$base&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routeParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$frontUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;_setRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// kept from original&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'route_path'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;unsetData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'route_path'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$routePieces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// additional logic here if needed to map route path to frontend routes&lt;/span&gt;
        &lt;span class="nv"&gt;$pieces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;yourFunctionForMappingRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routePieces&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'route_path'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$pieces&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;_getRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routeParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// use parent function if we're in admin scope&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;isAdmin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;_getRoutePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$routeParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;_getData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'route_path'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Closing remarks
&lt;/h2&gt;

&lt;p&gt;There will be plenty of edge-cases and unique needs in something like this, so your implementation will probably vary and grow over time. This is a simplified version of what we have implemented, as some of our needs were unique and not valuable to this tutorial.&lt;/p&gt;

&lt;p&gt;I am hoping one day Magento implements a built-in solution for these types of issues, especially with the PWA Studio being a thing now. Until then, we will continue to help each other find our own solutions.&lt;/p&gt;

&lt;p&gt;Do you have a better solution? Did this work for you? Let me know, I'd love to check out better solutions if possible!&lt;/p&gt;

</description>
      <category>magento</category>
      <category>ecommerce</category>
      <category>headless</category>
      <category>php</category>
    </item>
    <item>
      <title>Using field dependency in Magento 2</title>
      <dc:creator>Joel Rainwater</dc:creator>
      <pubDate>Wed, 16 Dec 2020 22:37:57 +0000</pubDate>
      <link>https://forem.com/rain2o/using-field-dependency-in-magento-2-2ca</link>
      <guid>https://forem.com/rain2o/using-field-dependency-in-magento-2-2ca</guid>
      <description>&lt;p&gt;Skip the details, show me the code&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;p&gt;One of the most common questions I see and have asked myself for Magento 2 is how to create fields that are dynamically visible, dependent on the value of another field. This article will walk through how to do this using the tools already provided by Magento.&lt;/p&gt;

&lt;p&gt;I have seen a lot of solutions out there, most of which involve building this feature yourself with some custom Javascript, which is just fine however I prefer to use the tools already provided by the system when available. This article is an extension of my answer on the Magento Stack Exchange here - &lt;/p&gt;
&lt;div class="ltag__stackexchange--container"&gt;
  &lt;div class="ltag__stackexchange--title-container"&gt;
    
      &lt;div class="ltag__stackexchange--title"&gt;
        &lt;div class="ltag__stackexchange--header"&gt;
          &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackexchange-logo-37d4118c1280b00533496a8c870284b2c0d08fac862f7cf964b9469b9db96984.svg" alt=""&gt;
          &lt;a href="https://magento.stackexchange.com/questions/132020/magento-2-1-how-do-i-create-form-component-field-custom-depends-on-another-field/256280#256280" rel="noopener noreferrer"&gt;
            &lt;span class="title-flare"&gt;answer&lt;/span&gt; re: Magento 2.1 How do I create form component field custom depends on another field value?
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="ltag__stackexchange--post-metadata"&gt;
          &lt;span&gt;Dec 31 '18&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;a class="ltag__stackexchange--score-container" href="https://magento.stackexchange.com/questions/132020/magento-2-1-how-do-i-create-form-component-field-custom-depends-on-another-field/256280#256280" rel="noopener noreferrer"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackexchange-arrow-up-eff2e2849e67d156181d258e38802c0b57fa011f74164a7f97675ca3b6ab756b.svg" alt=""&gt;
        &lt;div class="ltag__stackexchange--score-number"&gt;
          12
        &lt;/div&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackexchange-arrow-down-4349fac0dd932d284fab7e4dd9846f19a3710558efde0d2dfd05897f3eeb9aba.svg" alt=""&gt;
      &lt;/a&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--body"&gt;
    
&lt;p&gt;This is an old question with multiple answers that work, however I have discovered a solution using what Magento provides (as of 2.1.0) without the need for extending components. As multiple questions have been marked as duplicate and directed here I thought it would be beneficial to provide some information…&lt;/p&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--btn--container"&gt;
    &lt;a href="https://magento.stackexchange.com/questions/132020/magento-2-1-how-do-i-create-form-component-field-custom-depends-on-another-field/256280#256280" class="ltag__stackexchange--btn" rel="noopener noreferrer"&gt;Open Full Answer&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Behind the Curtain
&lt;/h2&gt;

&lt;p&gt;The steps in this article walk through how to implement this functionality. But to give you a high-level view at what functionality we will be taking advantage of, and where you can find it used in the wild (i.e. core Magento), here's some quick info.&lt;br&gt;
All form element ui components that extend &lt;code&gt;Magento_Ui/js/form/element/abstract.js&lt;/code&gt; have a &lt;code&gt;switcherConfig&lt;/code&gt; setting available for purposes such as hiding/showing elements as well as other actions. The switcher component can be found at &lt;a href="https://github.com/magento/magento2/blob/2.1/app/code/Magento/Ui/view/base/web/js/form/switcher.js" rel="noopener noreferrer"&gt;Magento_Ui/js/form/switcher&lt;/a&gt; for the curious. You can find examples of it being used in &lt;a href="https://github.com/magento/magento2/blob/2.1/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml#L158" rel="noopener noreferrer"&gt;sales_rule_form.xml&lt;/a&gt; and &lt;a href="https://github.com/magento/magento2/blob/2.1/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml#L213" rel="noopener noreferrer"&gt;catalog_rule_form.xml&lt;/a&gt;. Of course if you are using your own custom component already you can still use this as long as your component eventually extends &lt;code&gt;abstract&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;This article was written using Magento 2.3, however this feature has been available as of Magento 2.1.0.&lt;br&gt;
This article assumes you have a custom module you are implementing this functionality into. If you need help building a module, check out the &lt;a href="https://devdocs.magento.com/videos/fundamentals/create-a-new-module/" rel="noopener noreferrer"&gt;Magento dev docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set the Stage
&lt;/h2&gt;

&lt;p&gt;To make things easier, let's define some requirements for a feature we're going to build. I am going to use an example of something I built recently so that I can give real examples.&lt;/p&gt;

&lt;p&gt;Let's say you have created a new entity in Magento called a Shipping Carrier, and you have created a view in admin to edit these Shipping Carriers. To use the feature we are discussing in this article, you should be using Magento's &lt;a href="https://devdocs.magento.com/guides/v2.4/ui_comp_guide/bk-ui_comps.html" rel="noopener noreferrer"&gt;UI Components&lt;/a&gt;. More on that later.&lt;/p&gt;

&lt;p&gt;While editing a Shipping Carrier, we want the ability to enable Tracking for some carriers, and if Tracking is enabled we want to provide a Tracking URL. We will create two fields for this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tracking Enabled (&lt;code&gt;tracking_enabled&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Tracking URL (&lt;code&gt;tracking_url&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To make things cleaner for the content editors, we will hide the Tracking URL field unless Tracking Enabled is set to Enabled (yes this is redundant, but naming is the hardest part of being a developer, right?). Below is a screen recording of the end result we will be trying to accomplish.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbg8l4bmcx0bxa13nyejz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fbg8l4bmcx0bxa13nyejz.gif" alt="Editing a Shipping Carrier in Magento 2. There is a field titled " value=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the purposes of this article, I will skip the creation of a module and the creation of the Shipping Carrier controller and UI Component form, as this is focused on toggling visibility of fields in a UI Component form. You can find a number of articles showing &lt;a href="https://www.mageplaza.com/devdocs/creat-a-ui-form-in-magento-2.html" rel="noopener noreferrer"&gt;how to create UI Component forms&lt;/a&gt; in Magento 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do
&lt;/h2&gt;

&lt;p&gt;Finally, let's get started on the code. Once you have created your module and the appropriate controller and layout, you should have a file in your module like &lt;code&gt;{Namespace}/{ModuleName}/view/adminhtml/ui_component/shipping_carrier_form.xml&lt;/code&gt;. This file will change depending on what type of entity you are working on, and the name of the UI Component form you created. For the Product Edit form for example, it would be &lt;code&gt;product_form.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, let's add our two new fields to the &lt;code&gt;shipping_carrier_form&lt;/code&gt; UI Component form. The relevant part of the file will look something like this.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt; &lt;span class="na"&gt;xsi:noNamespaceSchemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;fieldset&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"carrier_information"&lt;/span&gt; &lt;span class="na"&gt;sortOrder=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tracking_enabled"&lt;/span&gt; &lt;span class="na"&gt;formElement=&lt;/span&gt;&lt;span class="s"&gt;"select"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        ...
      &lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;settings&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;validation&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"required-entry"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"boolean"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/validation&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dataType&amp;gt;&lt;/span&gt;number&lt;span class="nt"&gt;&amp;lt;/dataType&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tracking Enabled&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;scopeLabel&amp;gt;&lt;/span&gt;[WEBSITE]&lt;span class="nt"&gt;&amp;lt;/scopeLabel&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;visible&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/visible&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dataScope&amp;gt;&lt;/span&gt;tracking_enabled&lt;span class="nt"&gt;&amp;lt;/dataScope&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/settings&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;formElements&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;select&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;settings&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;options&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enabled&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disabled&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/options&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/settings&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/formElements&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tracking_url"&lt;/span&gt; &lt;span class="na"&gt;formElement=&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        ...
      &lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;settings&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;validation&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"required-entry"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"boolean"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/validation&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dataType&amp;gt;&lt;/span&gt;text&lt;span class="nt"&gt;&amp;lt;/dataType&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tracking URL&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;scopeLabel&amp;gt;&lt;/span&gt;[WEBSITE]&lt;span class="nt"&gt;&amp;lt;/scopeLabel&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;visible&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/visible&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dataScope&amp;gt;&lt;/span&gt;tracking_url&lt;span class="nt"&gt;&amp;lt;/dataScope&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/settings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So now we have the two fields always visible and always required for Shipping Carriers. To see the changes you may need to clear cache if you have it enabled.&lt;/p&gt;

&lt;p&gt;Now, let's get to the problem at hand. We want to hide the Tracking URL field unless Tracking is Enabled. At this point, we need to add a bit of XML into the controller field. By controller field, I mean the field that controls the visibility of the other field(s). In our case, this will be the field &lt;code&gt;tracking_enabled&lt;/code&gt;. We need to add &lt;code&gt;&amp;lt;switcherConfig&amp;gt;&lt;/code&gt; inside the &lt;code&gt;&amp;lt;settings&amp;gt;&lt;/code&gt; node of this field. It should look something like this.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;switcherConfig&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;rules&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;actions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;target&amp;gt;&lt;/span&gt;shipping_carrier_form.shipping_carrier_form.carrier_information.tracking_url&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;callback&amp;gt;&lt;/span&gt;hide&lt;span class="nt"&gt;&amp;lt;/callback&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/actions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;actions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;target&amp;gt;&lt;/span&gt;shipping_carrier_form.shipping_carrier_form.carrier_information.tracking_url&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;callback&amp;gt;&lt;/span&gt;show&lt;span class="nt"&gt;&amp;lt;/callback&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/actions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/rules&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;enabled&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/enabled&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/switcherConfig&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now the Tracking URL field will be hidden unless the Tracking Enabled field is set to Enabled. It will also only be required if it is visible. But let's break down what we just added so we can understand what it does.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;switcherConfig&amp;gt;&lt;/code&gt; component contains an array of rules which is what we're building out here. Each &lt;code&gt;&amp;lt;rule&amp;gt;&lt;/code&gt; has a name which is a number in this example. This name is the array index for this item. As these are arrays, they should start with 0, not strings or 1.&lt;/p&gt;

&lt;p&gt;Inside each &lt;code&gt;&amp;lt;rule&amp;gt;&lt;/code&gt; we pass two arguments.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;value&amp;gt;&lt;/code&gt; - This is the value of &lt;code&gt;tracking_enabled&lt;/code&gt; which should trigger the actions defined below.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;actions&amp;gt;&lt;/code&gt; - Here we have another array. These are the actions to be triggered when this rule's conditions are met. Again, each &lt;code&gt;action&lt;/code&gt;'s name is just the array index of that item.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now each &lt;code&gt;&amp;lt;action&amp;gt;&lt;/code&gt; has two arguments as well (with an optional 3rd).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;target&amp;gt;&lt;/code&gt; - This is the element you wish to manipulate under this action. If you aren't familiar with how &lt;code&gt;ui_component&lt;/code&gt; element names are composed in Magento you can check out &lt;a href="https://alanstorm.com/magento_2_introducing_ui_components/" rel="noopener noreferrer"&gt;Alan Storm's article&lt;/a&gt;. It's basically something like &lt;code&gt;{component_name}.{component_name}.{fieldset_name}.{field_name}&lt;/code&gt; in this example.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;callback&amp;gt;&lt;/code&gt; - Here is the action to be taken on the above mentioned target. This callback should be a JavaScript function that is available on the element targeted. Our example uses &lt;code&gt;hide&lt;/code&gt; and &lt;code&gt;show&lt;/code&gt;. This is where you can start to expand on the functionality available. The &lt;code&gt;catalog_rule_form.xml&lt;/code&gt; example I mentioned earlier uses &lt;code&gt;setValidation&lt;/code&gt; if you wish to see a different example.&lt;/li&gt;
&lt;li&gt;You can also add &lt;code&gt;&amp;lt;params&amp;gt;&lt;/code&gt; to any &lt;code&gt;&amp;lt;action&amp;gt;&lt;/code&gt; that calls for them. You can see this in the &lt;code&gt;catalog_rule_form.xml&lt;/code&gt; example as well.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally the last item inside &lt;code&gt;&amp;lt;switcherConfig&amp;gt;&lt;/code&gt; is &lt;code&gt;&amp;lt;enabled&amp;gt;true&amp;lt;/enabled&amp;gt;&lt;/code&gt;. This should be pretty straight forward, it's a Boolean to enable/disable the switcher functionality we just implemented.&lt;/p&gt;

&lt;p&gt;And we're done! The final XML for our new fields should look like this.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tracking_enabled"&lt;/span&gt; &lt;span class="na"&gt;formElement=&lt;/span&gt;&lt;span class="s"&gt;"select"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
  &lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;settings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;validation&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"required-entry"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"boolean"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/validation&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dataType&amp;gt;&lt;/span&gt;number&lt;span class="nt"&gt;&amp;lt;/dataType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tracking Enabled&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;scopeLabel&amp;gt;&lt;/span&gt;[WEBSITE]&lt;span class="nt"&gt;&amp;lt;/scopeLabel&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;visible&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/visible&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dataScope&amp;gt;&lt;/span&gt;tracking_enabled&lt;span class="nt"&gt;&amp;lt;/dataScope&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;switcherConfig&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;rules&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;actions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;target&amp;gt;&lt;/span&gt;shipping_carrier_form.shipping_carrier_form.carrier_information.tracking_url&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;callback&amp;gt;&lt;/span&gt;hide&lt;span class="nt"&gt;&amp;lt;/callback&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/actions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;actions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;action&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;target&amp;gt;&lt;/span&gt;shipping_carrier_form.shipping_carrier_form.carrier_information.tracking_url&lt;span class="nt"&gt;&amp;lt;/target&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;callback&amp;gt;&lt;/span&gt;show&lt;span class="nt"&gt;&amp;lt;/callback&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/actions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/rules&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;enabled&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/enabled&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/switcherConfig&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/settings&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;formElements&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;select&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;settings&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;options&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"0"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enabled&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"number"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;0&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disabled&lt;span class="nt"&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/options&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/settings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/formElements&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tracking_url"&lt;/span&gt; &lt;span class="na"&gt;formElement=&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;argument&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"array"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ...
  &lt;span class="nt"&gt;&amp;lt;/argument&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;settings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;validation&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"required-entry"&lt;/span&gt; &lt;span class="na"&gt;xsi:type=&lt;/span&gt;&lt;span class="s"&gt;"boolean"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/validation&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dataType&amp;gt;&lt;/span&gt;text&lt;span class="nt"&gt;&amp;lt;/dataType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;translate=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tracking URL&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;scopeLabel&amp;gt;&lt;/span&gt;[WEBSITE]&lt;span class="nt"&gt;&amp;lt;/scopeLabel&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;visible&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/visible&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dataScope&amp;gt;&lt;/span&gt;tracking_url&lt;span class="nt"&gt;&amp;lt;/dataScope&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/settings&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Good luck with your dynamic fields! Feel free to post comments if this article helped you build something, or if you have any questions or other feedback.&lt;/p&gt;

</description>
      <category>magento2</category>
      <category>magento</category>
      <category>xml</category>
      <category>php</category>
    </item>
  </channel>
</rss>
