---
url: /custom-modules/routing.md
---
# Module Routing

Custom modules can define their own routes in the `routes.php` file. Evolve will automatically load these routes when the module is registered.

::: warning Important
If your service provider extends the `ModuleServiceProvider`, your module routes will be expected in `src/Http/routes.php`.
:::

## Admin routes

Evolve provides a `evolveModule` macro on the `Route` facade to group module routes:

```php[routes.php]
Route::evolveModule('blog', function () {
    Route::resource('/post', PostController::class);
    // ...
```

Routes wrapped with this macro will have URLs prefixed with `evolve/[module-name]/*` and use the `web` and `auth` middlewares. Also the route names will be prefixed with `[module-name].*`

## Routeable models

You can dynamically make your models *routeable* by implementing the `Routeable` interface and using the `HasFrontendRoute` trait:

```php[Post.php]
use Illuminate\Database\Eloquent\Model;
use Racerfish\Evolve\Contracts\Routeable;
use Racerfish\Evolve\Concerns\HasFrontendRoute;

class Post extends Model implements Routeable
{
    use HasFrontendRoute;
```

When new entities of this type are stored in the database, Evolve will also create a slug based on the `title` or `name` property from your model.

**Example:** A new blog post with the title "*Lorem Ipsum*" creates a new route `de/lorem-ipsum`. (Translated models are always prefixed with the correct locale)

### Customizing the route generation

You can override following functions from the `HasFrontendRoute` trait to customize the page title and slug for your model:

* `getPageTitle`
* `getRoutePath`

### Defining model visibility

Sometimes you need to define the visibility of models based on some active/inactive, published/unpublished states. You can easily make this by defining a `public` query builder scope on your model.

Models which can't be queried with the `public` scope will return a `404 Not Found` for guest users.

```php
public function scopePublic(Builder $query, bool $withHidden = false)
{
    return $query->where('is_active', true);
}
```

> Use the `$withHidden` flag to define visibiliy for hidden but not private access.

## Other frontend routes

Define other frontend routes the usual Laravel way, in the project's `routes/web.php` or in a package's service provider. Always wrap them into the `web` middleware.

```php[routes/web.php]
Route::middleware('web')->get('/screen/{key}', ScreenController::class);
```

### Route order and the catch-all

Evolve resolves its pages through a catch-all route `{path?}` named `route.resolve`. Since Evolve 2.21 it registers this route after all service providers have booted, so it is always the last route of the application. Routes from `routes/web.php`, from a module and from any package win over it, no matter in which order the providers boot. This also holds for cached routes (`php artisan route:cache`).

Before 2.21 the catch-all was loaded during Evolve's own boot. Routes registered later never matched when their path looked like a page path, so projects registered them in a `booting` callback or in `register()`. Once a project requires `racerfish/evolve:^2.21`, move such routes back to `routes/web.php` or the provider's `boot()` method.

::: warning
A route of your own now takes precedence over an Evolve page with the same path. If an editor creates a page whose URL equals one of your routes, the page is no longer reachable.
:::
