<p>The example given by Robin didn't work for me. The action param needs to be a param of the actionIndex function:</p>

<pre><code>public function actionIndex($action){
...
}
</code></pre>

CC BY-SA 3.0
2018-04-24T08:31:46.850Z
Indeed they changed the behavior about 5 months ago. Thanks for noting, I forgot about it
Robin Schambach
CC BY-SA 3.0
2018-04-19T15:02:17.593Z
2018-04-19T15:02:17.593Z
Jeroen
<h2>EDIT:</h2>
<p>I'm so sorry - I messed it up... I rechecked the craft code and I was all the way wrong with the first answer. I hope you had no trouble because of me.</p>
<p>The correct answer is this one....</p>
<pre><code>Event::on(UrlManager::class, UrlManager::EVENT_REGISTER_SITE_URL_RULES, function(RegisterUrlRulesEvent $event) {
    $event-&gt;rules['redirect/&lt;action:\w+&gt;'] = 'customer-portal/portal/';
    $event-&gt;rules['checkout']              = 'customer-portal/portal/checkout';
});
</code></pre>
<p>and in your controller</p>
<pre><code>public function actionIndex(){
    $request = Craft::$app-&gt;getRequest();
    $method = $request-&gt;getParam('action');
    return $this-&gt;redirect($method);
}
</code></pre>
<p>In order to have access to the backend function you need to make it &quot;public&quot; so you have to include a <code>protected $allowAnonymous = true;</code> or an array with all functions <code>protected $allowAnonymous = ['functionOne', 'functionTwo'];</code>
Your Controller could look like this</p>
<pre><code>class UserController extends Controller {
    protected $allowAnonymous = true;

    // pretend the request is /redirect/foo and we have created the rule 
    // $event-&gt;rules['foo']                    = 'customer-portal/user/foo'; and
    // $event-&gt;rules['redirect/&lt;action:\w+&gt;']  = 'customer-portal/user/redirect';
    public function actionRedirect(){
        $request = Craft::$app-&gt;getRequest();
        $method = $request-&gt;getParam('action');
        return $this-&gt;redirect($method);
    }

    public function actionFoo(){
        die(&quot;foo&quot;);
    }

}
</code></pre>
<p>Old Post... ignore this</p>
<p><s>It's not a bug, that's the way how craft handles it. Unlike in other cms plugins in craft are not supposed to handle entire frontend page requests in order to render the entire page.
You can access actions from controllers only via the /action url. Everything else is handled by entries/elements</p>
<p>So you'll not be able to achieve what you want since craft tries to access an entry when you don't include an action url. All frontend request without an action url need to be related to an entry/element or a category. So when you try to access <code>/redirect</code> craft will search for an entry with the slug <code>redirect</code></p>
<p>What you want is the following:</p>
<ol>
<li>creating an entry with the slug &quot;redirect&quot;</li>
<li>$entry = Craft::$app-&gt; //your logic to fetch this specific entry</li>
<li>$this-&gt;redirect($entry-&gt;url)</li>
</ol>
<p>When you want to create a plugin for other people to use it, you have to create a settings page, include a relation field to that and let other people link an entry to the redirect -&gt; you can fetch this entry from your settings and redirect to it.</p>
<p>The other thing... <code>$event-&gt;rules['portal/&lt;action&gt;'] = 'customer-portal/portal/&lt;action&gt;';</code> is wrong.. Take a look at this <a href="https://github.com/craftcms/docs/blob/master/en/updating-plugins.md#registersiteroutes" rel="nofollow noreferrer">documentation</a><br>
<code>$event-&gt;rules['portal/&lt;action:\w+&gt;'] = 'customer-portal/portal/handle-redirect';</code>
and in your portalController</p>
<pre><code>public function actionHandleRedirect(){
    $request = Craft::$app-&gt;getRequest();
    $function = &quot;action&quot; . $request-&gt;getParam('action');
    $this-&gt;$function();
}
</code></pre>
<p>But I would suggest you not to do that and use the craft way to handle these things</s></p>

CC BY-SA 3.0
2017-10-19T22:26:39.390Z
2017-10-20T06:44:44.773Z
2020-06-17T08:56:27.903Z
Robin Schambach
<p>So I am working on converting a previously-standalone Yii 2 app into a Craft module and am having some issues with routing.  I wish to keep my old URL structure as much as possible.  As such, I created a plugin and register routes.  However, I've noticed an issue: When I call Url::to(), I get back URLs that seem to rely on the actionTrigger parameter, even if I explicitly defined routes in my plugin.</p>

<p>Here are the routes as I have them in my plugin:</p>

<pre><code>class CustomerPortal extends \craft\base\Plugin
{
    public function init()
    {
        Event::on(
            UrlManager::class,
            UrlManager::EVENT_REGISTER_SITE_URL_RULES,
            function(\craft\events\RegisterUrlRulesEvent $event) {
                $event-&gt;rules['portal/&lt;action:[\w-+]&gt;'] = 'customer-portal/portal/&lt;action&gt;';
</code></pre>

<p>If I make the call in a controller to redirect to a route, it builds the URL using an action param, not the rules we defined.  For example:</p>

<pre><code>class UserController extends \craft\web\Controller
{
    public function actionRedirect()
    {
        echo Url::to(['checkout');
        // I'd expect to see '/checkout', but instead we get 
        // '/actions/customer-portal/user/checkout' since we aren't
        // applying either of our rules.
    }

    public function actionCheckout()
    {
    }
</code></pre>

<p>It seems that the rules are checked when parsing, but not during URL creation, and if you come in with an action prefix, the rules aren't parsed then, but the raw action is attempted.  </p>

<p>Am I missing something or is this a bug and either way, can anyone think of a way to work around it?</p>

<p>Edit: Figured out the issue with routes not resolving I had originally, it turns out in the end, my issues was that the regex in the rules wasn't accounting for the fact that many of routes have dashes in them (for example: /user/billing-info).  The default Yii docs say to use <code>&lt;action:\w+&gt;</code>, but that doesn't match the dash character in the URL.  Using <code>&lt;action:[\w-]+&gt;</code> works perfectly and makes this all work.</p>

<p>That said, I'm still not getting Url::to or anything else that references UrlManager to work properly since UrlManager seems to add the action param to the URL before checkout to see if we match existing routes.</p>

CC BY-SA 3.0
2017-10-21T22:10:58.357Z
Craft checks for your custom routes before adding an action param. You just have to declare the route correctly.
Robin Schambach
<plugin-development><craft3><yii>
Craft 3 Plugin Routing Problems
# Craft 3 Plugin Routing Problems

(Asked by: username_0 on 2017-10-19)

So I am working on converting a previously-standalone Yii 2 app into a Craft module and am having some issues with routing.  I wish to keep my old URL structure as much as possible.  As such, I created a plugin and register routes.  However, I've noticed an issue: When I call Url::to(), I get back URLs that seem to rely on the actionTrigger parameter, even if I explicitly defined routes in my plugin.
Here are the routes as I have them in my plugin:
class CustomerPortal extends \craft\base\Plugin
{
    public function init()
    {
        Event::on(
            UrlManager::class,
            UrlManager::EVENT_REGISTER_SITE_URL_RULES,
            function(\craft\events\RegisterUrlRulesEvent $event) {
                $event->rules['portal/'] = 'customer-portal/portal/';

If I make the call in a controller to redirect to a route, it builds the URL using an action param, not the rules we defined.  For example:
class UserController extends \craft\web\Controller
{
    public function actionRedirect()
    {
        echo Url::to(['checkout');
        // I'd expect to see '/checkout', but instead we get 
        // '/actions/customer-portal/user/checkout' since we aren't
        // applying either of our rules.
    }

    public function actionCheckout()
    {
    }

It seems that the rules are checked when parsing, but not during URL creation, and if you come in with an action prefix, the rules aren't parsed then, but the raw action is attempted.  
Am I missing something or is this a bug and either way, can anyone think of a way to work around it?
Edit: Figured out the issue with routes not resolving I had originally, it turns out in the end, my issues was that the regex in the rules wasn't accounting for the fact that many of routes have dashes in them (for example: /user/billing-info).  The default Yii docs say to use , but that doesn't match the dash character in the URL.  Using  works perfectly and makes this all work.
That said, I'm still not getting Url::to or anything else that references UrlManager to work properly since UrlManager seems to add the action param to the URL before checkout to see if we match existing routes.

**Tags:** plugin-development, craft3, yii
> **Comments:**
> **username_1**: Craft checks for your custom routes before adding an action param. You just have to declare the route correctly.  

---

## 2 answers

### Answer by username_2 on 2018-04-19. Score: 1

The example given by Robin didn't work for me. The action param needs to be a param of the actionIndex function:
public function actionIndex($action){
...
}

> **Comments:**
> **username_1**: Indeed they changed the behavior about 5 months ago. Thanks for noting, I forgot about it  

---

### Answer by username_1 on 2017-10-19. Score: 1

EDIT:
I'm so sorry - I messed it up... I rechecked the craft code and I was all the way wrong with the first answer. I hope you had no trouble because of me.
The correct answer is this one....
Event::on(UrlManager::class, UrlManager::EVENT_REGISTER_SITE_URL_RULES, function(RegisterUrlRulesEvent $event) {
    $event->rules['redirect/'] = 'customer-portal/portal/';
    $event->rules['checkout']              = 'customer-portal/portal/checkout';
});

and in your controller
public function actionIndex(){
    $request = Craft::$app->getRequest();
    $method = $request->getParam('action');
    return $this->redirect($method);
}

In order to have access to the backend function you need to make it "public" so you have to include a protected $allowAnonymous = true; or an array with all functions protected $allowAnonymous = ['functionOne', 'functionTwo'];
Your Controller could look like this
class UserController extends Controller {
    protected $allowAnonymous = true;

    // pretend the request is /redirect/foo and we have created the rule 
    // $event->rules['foo']                    = 'customer-portal/user/foo'; and
    // $event->rules['redirect/']  = 'customer-portal/user/redirect';
    public function actionRedirect(){
        $request = Craft::$app->getRequest();
        $method = $request->getParam('action');
        return $this->redirect($method);
    }

    public function actionFoo(){
        die("foo");
    }

}

Old Post... ignore this
It's not a bug, that's the way how craft handles it. Unlike in other cms plugins in craft are not supposed to handle entire frontend page requests in order to render the entire page.
You can access actions from controllers only via the /action url. Everything else is handled by entries/elements
So you'll not be able to achieve what you want since craft tries to access an entry when you don't include an action url. All frontend request without an action url need to be related to an entry/element or a category. So when you try to access /redirect craft will search for an entry with the slug redirect
What you want is the following:

creating an entry with the slug "redirect"
$entry = Craft::$app-> //your logic to fetch this specific entry
$this->redirect($entry->url)

When you want to create a plugin for other people to use it, you have to create a settings page, include a relation field to that and let other people link an entry to the redirect -> you can fetch this entry from your settings and redirect to it.
The other thing... $event->rules['portal/'] = 'customer-portal/portal/'; is wrong.. Take a look at this documentation
$event->rules['portal/'] = 'customer-portal/portal/handle-redirect';
and in your portalController
public function actionHandleRedirect(){
    $request = Craft::$app->getRequest();
    $function = "action" . $request->getParam('action');
    $this->$function();
}

But I would suggest you not to do that and use the craft way to handle these things


---

