Skip to content

Installation

The quickest way to a new application is the Anvil AppStarter: a CodeIgniter project with Anvil, Shield, a strict security configuration, scheduled commands, tests and one example resource. Follow its README; it ends with step 6 below. The Anvil Demo shows most features with example data.

This page describes how to add Anvil to an existing CodeIgniter application.

Software Version
PHP 8.5 or newer, with intl, mbstring and sodium
CodeIgniter 4.7 or newer
MySQL 8.0 or newer, 8.4 LTS for production

Anvil brings CodeIgniter Shield, Settings and Queue as dependencies.

In the composer.json of your application, add a path repository that points to your local Anvil checkout, and publish the assets after every install and update:

{
"repositories": [
{
"type": "path",
"url": "../Anvil",
"options": { "symlink": true }
}
],
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"post-install-cmd": ["@php spark anvil:publish"],
"post-update-cmd": ["@php spark anvil:publish"]
}
}

Then require it:

Terminal window
composer require rakoitde/anvil:@dev

With symlink: true, changes in the Anvil checkout are visible in your application immediately. anvil:publish copies the prebuilt CSS, JavaScript and fonts to public/assets/anvil (Config\Anvil::$assetsPath).

Anvil derives its keys (audit chain, component tokens, …) from the CodeIgniter encryption key. It must be at least 32 bytes long; the install command refuses to run otherwise.

Terminal window
php spark key:generate
Terminal window
php spark shield:setup

This publishes Config\Auth, Config\AuthGroups and Config\AuthToken and adds Shield’s routes to app/Config/Routes.php. Self-registration is off in Anvil applications, so exclude its route:

service('auth')->routes($routes, ['except' => ['register']]);

Then adjust the configuration:

File Setting
app/Config/Auth.php $views['login'] = '\Anvil\Views\auth\login', $views['layout'] = '\Anvil\Views\auth\layout', $allowRegistration = false, $minimumPasswordLength = 12
app/Config/Security.php $csrfProtection = 'session', $regenerate = false
app/Config/Filters.php global csrf before, global secureheaders after; Shield’s login throttle in $filters (below)
.env app.CSPEnabled = true

The Anvil login view adds the single sign-on buttons and hides the password form when Config\Anvil::$passwordLogin is false; the layout gives the login, two-factor and password pages the look of the panels.

$regenerate = false keeps the CSRF token stable within a session, so that the htmx requests of a page do not invalidate each other.

app/Config/Filters.php
public array $globals = [
'before' => [
// The API authenticates with tokens, without cookies or session.
'csrf' => ['except' => ['api/*']],
'invalidchars',
],
'after' => [
'secureheaders',
],
];
public array $filters = [
// Shield's throttle of logins, magic links and the TOTP step (per IP).
'auth-rates' => ['before' => ['login*', 'register', 'auth/*']],
];

Leave out the api/* exception when you do not use the API. With a SAML provider, also exclude sso/*/callback: the identity provider posts its answer without your CSRF token.

See Styling for the Content Security Policy.

Terminal window
php spark make:anvil-panel AdminPanel

This creates app/Panels/AdminPanel.php with the id and path admin. Add the user and role administration of Anvil to it:

use Anvil\Access\Access;
use Anvil\Page\Dashboard;
use Anvil\Panel\Panel;
final class AdminPanel extends Panel
{
public function id(): string
{
return 'admin';
}
public function path(): string
{
return 'admin';
}
public function brand(): string
{
return 'My App';
}
public function resources(): array
{
return [...Access::resources()];
}
public function pages(): array
{
return [Dashboard::class, ...Access::pages()];
}
}

Register it in app/Config/Anvil.php. This class extends Anvil’s configuration and holds all your settings:

<?php
namespace Config;
use Anvil\Config\Anvil as BaseAnvil;
use App\Panels\AdminPanel;
class Anvil extends BaseAnvil
{
public array $panels = [AdminPanel::class];
}

Panels are registered explicitly; Anvil never scans the file system. More in Panels.

Anvil is multi-tenant by default: every record belongs to a tenant and users only see the tenants they are members of. Keep it on, also with a single organisation: the install command creates one tenant, and adding a second one later is a single command. Switch it off only for applications that will never have tenants, and do it before installing:

app/Config/Anvil.php
public bool $multiTenancy = false;

Switching later needs a data migration (anvil:tenancy:enable, anvil:tenancy:disable), see Without tenants.

Terminal window
php spark anvil:install --tenant "My Company" --email admin@example.com

The command

  1. checks the encryption key,
  2. runs all migrations (migrate --all: Shield, Settings, Anvil and your own),
  3. publishes the assets (anvil:publish),
  4. creates the first tenant (only with multi-tenancy),
  5. creates the administrator with a generated password, which is shown once and must be changed at the first login,
  6. creates the role Administrator with full access and assigns it.
Option Default
--tenant Default
--username admin
--email asked for
--group superadmin (Shield group of the administrator)
--promote give an existing user with this e-mail address full access without asking

The command can be run again: an existing tenant and role are reused. An existing user only gets full access after a confirmation or with --promote, so a typo in the e-mail address cannot silently promote somebody else.

Start the server and sign in at /admin:

Terminal window
php spark serve

All options live in Anvil\Config\Anvil; override them in app/Config/Anvil.php or in .env:

anvil.passwordLogin = false
anvil.mfaRequired = true

See Configuration.

Topic Setting
HTTPS Only HTTPS; HSTS at the web server or proxy; app.baseURL with https:// (single sign-on and SAML rely on it)
Proxies Config\App::$proxyIPs with the addresses of your reverse proxies, otherwise rate limits and audit IPs see only the proxy
CSP and headers app.CSPEnabled = true with the policy of Styling; Anvil itself sets X-Frame-Options, X-Content-Type-Options and Referrer-Policy on panel pages
CSRF global csrf filter (except api/* with the API, sso/*/callback with SAML, sso/*/backchannel-logout with an OpenID Connect back channel)
Login throttle Shield’s auth-rates filter on login*, register, auth/*
Sessions Config\Session with a lifetime that matches your policy; database or Redis handler with several servers
Cache a real cache handler (not dummy): rate limits and SSO state live there
Keys encryption.key and anvil.auditKey in a secret store, never in the repository
Audit php spark anvil:audit:verify in monitoring; database user with INSERT/SELECT on anvil_audit_log only
Jobs php spark anvil:prune daily, php spark queue:work anvil for large exports
Debug CI_ENVIRONMENT = production (no toolbar, no error details)

More in Deployment.

Create your first model, then your first resource.