Skip to content

Powered by Grav

Developing your site

Developing your site

Your instance is tuned for production. Caches are on, aggregation is on, errors are hidden, and a fast cache backend (Valkey) sits in front of the database. That is exactly what you want for a live site and exactly what gets in your way when you are building a theme or a module and need to see every change immediately.

The good news: you don't have to fight any of that, and you never need root or your host to do it. BOA gives you a development URL for every site, and everything you do on this page happens in your own shell (as oN.ftp) with Drush, plus a couple of files you are allowed to edit. Let's walk through it.

The .dev. preview URL

The trick that makes all of this pleasant is a special address. When you visit your site through a hostname that has .dev. in it, BOA quietly switches that page into development mode for you:

  • errors are shown on screen instead of hidden,
  • the internal page cache and CSS/JS aggregation are turned off,
  • opcode caching revalidates on every request, so edited PHP is picked up straight away.

Your normal live address is untouched the whole time. Real visitors keep seeing the fast, cached, error-free production site. Only requests that come in through the .dev. name get the development treatment. That means you can debug on the same running site, with the same database and files, without putting the live site into a fragile state.

Turning the development URL on

The development URL isn't magic wiring you have to request from anyone. It is just an alias of your site whose name contains .dev., and you add it yourself on the site's edit form in the control panel, the same place you add any other alias (see Site aliases and redirects).

Add an alias in this shape, with dev sitting between two dots:

TXT
www.dev.example.com

The .dev. in the middle is what both BOA's front end and the web server look for. A name like dev.example.com (with dev only at the start) will also flip the page into development mode, but the reliable, always-served form is the one with .dev. in the middle, so prefer www.dev.example.com or staging.dev.example.com.

A few practical notes:

  • Point the DNS for that name at your instance just like any other alias, or add it to your workstation's hosts file if it is only for your own eyes.
  • If your site has SSL enabled, the .dev. alias is covered too. BOA folds any alias with .dev. (or .devel.) in the middle into the site's HTTPS server and into its automatic Let's Encrypt certificate, so https://www.dev.example.com just works — no separate certificate to request. (The dev/devel exception only skips certificates when a site's main name is a dev name, not for a .dev. alias of a normal site.)
  • Nothing about the production site changes. You are adding a second door, not rebuilding anything.

The .dev. debug headers — read your live settings at a glance

Responses on a .dev. name carry a family of X-* debug headers that exist only there — never on your live URLs. The most useful set is X-Ini-*, which makes reading your effective INI configuration easy: one header per active value, plus X-Ini-Loc-Src and X-Ini-Plr-Src naming the exact site or platform file that supplied it (X-Ini-Src: BOA-Default means neither is set). X-Valkey-Prefix shows the cache namespace the site resolved to, and X-Base-Url / X-Site-Subdir the URL plumbing:

BASH
curl -sI https://www.dev.example.com/ | grep -i x-ini

The one exception to only-on-.dev. runs the other way: the X-Cache-* verify headers (see the object cache) are gated by the redis_debug_header INI setting instead, precisely so you can check caching on your production URLs.

Search engines can't see it — but treat the name as a secret anyway

You don't need to worry about your development URL leaking into search results. The web server recognizes the known crawlers by their user-agent — Google, Bing, Yandex, Baidu, and the generic bot/spider/crawler signatures — and answers them with a plain 404 on any dev-style hostname. To a search engine, the development view of your site simply doesn't exist, so nothing from it gets indexed.

People are a different matter. Development mode puts PHP errors on the page, and errors can reveal more than you'd like — file paths, module names, sometimes a fragment of configuration. Anyone who knows (or guesses) the dev name sees all of it, from anywhere. Two easy habits keep that a non-issue:

  • Treat the dev name like a password — don't publish or share it beyond the people doing the work. If it only ever needs to work for you, skip public DNS entirely and put the name in your workstation's hosts file.
  • Delete the alias when a stretch of development work is done. It's one line removed on the site's edit form, and re-adding it later takes seconds. A dev door that doesn't exist can't be found.

Twig debugging and turning off the cache backend (Drupal 8+)

On the .dev. URL, BOA already relaxes Drupal's page cache and aggregation for you. Two things it does not turn on by itself, because they are choices only you should make, are Twig template debugging and bypassing the Valkey cache backend. You switch both on with one small file that you drop into your own site's files directory.

Create this file (from your oN.ftp shell, or over SFTP):

TXT
sites/example.com/files/development.services.yml

with these contents:

YAML
parameters:
  http.response.debug_cacheability_headers: true
  twig.config:
    debug: true
    auto_reload: true
    cache: true
services:
  cache.backend.null:
    class: Drupal\Core\Cache\NullBackendFactory

Here is what each part buys you, and what BOA does around it:

  • The services: block defines a "do nothing" cache backend. Just having this file present on a .dev. request tells BOA to load it and to route Drupal's render and dynamic page caches through that null backend instead of Valkey. So while you develop on the .dev. URL your edits aren't served from the cache layer, and you don't have to touch any cache configuration yourself.
  • twig.config: debug: true makes Drupal annotate the rendered HTML with the template file names and theme suggestions for every region and block, which is the whole point of Twig debugging.
  • Leave auto_reload: true and cache: true as shown. BOA's own guidance is to keep the Twig cache on: auto_reload already re-renders a template the moment its source changes, and turning the cache fully off just makes every page slower for no extra benefit.

One catch to remember. After you first add (or later remove) this file you must clear the site's caches once, or the .dev. URL can answer with an error 500 complaining about a missing cache.backend.null service. Clear caches either from the control panel with the Flush all caches task on the site, or from your shell:

SH
drush @example.com cr

Then load https://www.dev.example.com and you'll see the Twig annotations in the page source.

Theme debugging on Drupal 7

Drupal 7 has no Twig, so the file above doesn't apply. The .dev. URL still relaxes caching and shows errors for you exactly the same way. For template-level debugging, Drupal 7 has its own switch you can flip with Drush from your shell:

SH
drush @example.com vset theme_debug 1
drush @example.com cc all

Theme hook suggestions now appear as HTML comments around each rendered element. Turn it back off when you're done:

SH
drush @example.com vset theme_debug 0
drush @example.com cc all

Sass / Compass: Ruby tooling in your shell

If your theme builds its CSS with Sass or Compass, you can run that toolchain directly in your oN.ftp shell — no compile-locally-and-upload loop. Ruby and the gem machinery are provided by the server; you switch them on for your account with a single control file:

SH
touch ~/static/control/compass.info

Then log out, give it up to five minutes, and log back in. The tooling is installed for your account in the background, and a fresh login picks up the new paths. A few gems can't build their native extensions inside the limited shell, so BOA pre-installs those problem gems for you automatically as part of that setup — you don't have to do anything about them. From there it's ordinary gem work:

SH
gem install --conservative compass
cd ~/platforms/my-platform/sites/all/themes/mytheme
bundle install     # if the theme ships a Gemfile — Bundler comes preinstalled
compass watch      # or: compass compile

gem-list shows the gems your account has — run it after your account is initialized, though: before then it can still show an old, system-wide gem list, and only once your account is set up does it list just your own local Ruby and gems. Run the tools directly (compass watch, guard start) rather than through a Drush wrapper — wrappers that open a sub-shell (like drush omega-guard) don't work in the limited shell.

The control file enables Ruby for your main account and any client sub-accounts on your instance — and because each sub-account gets its own copy of the gems, initialization takes a little longer the more sub-accounts you have. Deleting ~/static/control/compass.info removes the gems from all of them again a few minutes later.

A few notes:

  • If you've been around BOA long enough to remember RVM: it's gone. The old per-user RVM setup was replaced by a single server-provided Ruby, so any guide that begins "install RVM" no longer applies — the control file above is the entire setup.
  • If gem still isn't available well after five minutes and a fresh login, the Ruby toolchain may simply not be enabled on your server — it's an optional server-side extra. A short support request to your host sorts that out.
  • If bundle install says it can't build a native extension for a gem that gem-list already shows as installed, compare the versions — the copy BOA pre-installed often differs from the one pinned in your theme's Gemfile.lock. Point the lock file at the installed version and run bundle install again, or reinitialize Ruby to land on a different build: delete ~/static/control/compass.info, wait until compass --version stops working, then touch the file again.

What you may edit, and what BOA rewrites

This is the part people trip on most often, so it's worth being clear. The site's PHP settings files are kept under your instance's own control, and they are reset for you on a schedule. Knowing which file to reach for saves a lot of head-scratching.

Leave these alone. BOA owns them and rewrites them:

  • sites/example.com/settings.php — regenerated every time the site is verified. It's the glue that wires the site to its database, its cache, and the shared BOA settings. Editing it is temporary at best and can break the site at worst.

Your first stop for a setting change: the site control file.

Most of the per-site knobs you'd once have reached into PHP for now live in a plain, commented INI file that is genuinely yours to edit from your shell or over SFTP:

TXT
sites/example.com/modules/boa_site_control.ini

BOA drops this file in for you the first time it tidies the site, group-writable so you can open it and change values. It's where you toggle things like which cache bins skip the cache backend (redis_exclude_bins), or turn the backend off site-wide (redis_cache_disable), without touching any PHP. These keys keep the redis_ prefix even on a server running Valkey — BOA uses a single redis_ namespace for both, so don't go looking for a valkey_ toggle. Open the file, read the comments, uncomment the line you want, and save. This is the supported, no-surprises place for a hosted site's overrides.

About local.settings.php and drushrc.php. These two PHP files also exist in your site directory, and BOA does not overwrite their contents — but it does reset their permissions back to read-only on every verify and again overnight, and they're owned by the site's system user rather than your oN.ftp shell user. So they are not files you can just open and edit the way you can the INI above. settings.php includes local.settings.php last, after everything else, which is what makes it the ultimate override point. To change it yourself, follow BOA's supported procedure: create the control file sites/example.com/modules/local-allow.info, run the Reset password task to make local.settings.php group-writable, edit it, test as admin, then run Verify to restore permissions — or just describe the override you need and open a support request. Take care: a PHP error in local.settings.php that you haven't verified as admin first can cause a permanent white screen. For the everyday settings, reach for boa_site_control.ini first — that's the one built for you to change.

There's a fuller tour of which file to reach for in Which file do I edit?.

Your actual code — the modules and themes under sites/all/ or sites/example.com/, and anything in a platform you built yourself — is entirely yours to edit in place. BOA only manages the settings wrappers; it doesn't rewrite your modules, themes, or libraries.

Where your own modules and themes belong

On a platform you built yourself, this question answers itself: it's your codebase, so code goes into the codebase — composer require it, or commit it to your repo, and rebuild the platform when you update. The interesting case is a built-in platform — one your host provides — because there you can't touch most of the tree, and what you can touch comes in two flavours with very different behaviour.

What's locked, and what's open. On a built-in platform, Drupal core and the installation profile are read-only and root-owned — you can't overwrite a core file, patch core, or edit the profile, and that's enforced, not just discouraged. What stays writable for you is the sites/ tree: the shared sites/all/modules, sites/all/themes and sites/all/libraries folders, plus each of your sites' own sites/example.com/ folders. If your project genuinely needs a patched core, that's the signal you've outgrown built-in platforms — build your own instead (Building your own platform), where the whole tree is yours.

sites/all — the platform-wide level, and how overriding works. Code you put under sites/all/modules is seen by every site on the platform, and thanks to Drupal's own precedence rules it wins over code shipped deeper in the platform. That's the supported way to override one of the extra contrib modules BOA bundles with its built-in platforms, Drupal 6 through 11 (they live in shared o_contrib* areas, deliberately outside the profiles): upload your preferred version of the module to sites/all/modules/ and Drupal uses yours, no server-side changes needed.

After adding or overriding code, rebuild. Drupal caches where every module lives, so freshly placed code isn't picked up until that map is rebuilt. The control panel gives you one button that does the right thing on every Drupal version: the Rebuild registry task on the site rebuilds the registry on Drupal 6/7, and on Drupal 8 and newer rebuilds the compiled service container and flushes every cache. From your shell, drush @example.com cr (Drupal 8+) or drush @example.com cc all (Drupal 7) covers the everyday cases.

sites/example.com/modules — the site-local level, with one big caveat. Each site's own folder also has modules, themes and libraries subfolders, created for you writable on purpose — this is a supported place for code, and the right one for anything truly specific to a single site (a site-only glue module, a theme no other site uses). It's also how you can test a module or theme on one site of a shared platform without touching the others, since the site level takes precedence over sites/all.

The caveat: site-local code travels with the site, and no platform move ever updates it. When a site is cloned or migrated, its whole sites/example.com/ folder — modules included — is carried to the destination as-is. Migrate the site onto a shiny new platform and the new codebase is current everywhere except that folder: whatever versions you parked there follow the site around, ageing quietly, entirely yours to remember and update by hand. An outdated site-local copy also shadows the newer copy a platform may ship — the precedence that makes overrides work cuts both ways. So keep the site level for genuinely site-specific code and short-lived tests; anything shared, security-sensitive, or meant to be refreshed when platforms are upgraded belongs at sites/all — or better, baked into a platform you build and rebuild yourself.

Getting the aggregation toggle back on the live URL

On the .dev. URL, CSS/JS aggregation is already off for you. But one classic situation needs it off (or at least toggleable) where your visitors are: a theme or module ships styles or scripts that only misbehave when aggregated, and until it's fixed you want to flip aggregation off from Drupal's admin Performance page — on the live URL.

Out of the box that toggle appears to do nothing. BOA hardcodes aggregation on for every non-dev request, deliberately, so a site can't quietly run slow with aggregation forgotten off. The override is still self-service, though, and it lives in local.settings.php — the file from the section above, which loads after all of BOA's shared settings and therefore has the last word.

Following the local.settings.php editing procedure above, add these lines — on Drupal 6/7:

PHP
unset($conf['preprocess_css']);
unset($conf['preprocess_js']);

or on Drupal 8 and newer:

PHP
unset($config['system.performance']['css']['preprocess']);
unset($config['system.performance']['js']['preprocess']);

Note what this does: it doesn't switch aggregation off — it stops BOA from forcing it on, which hands control back to the checkboxes on the Performance admin page. Toggle them there as your fix-up work needs. When the underlying CSS/JS problem is solved, remove the lines again (the same edit procedure) so the enforced default can keep protecting the site's speed.

A comfortable loop

Putting it together, a typical session looks like this:

  1. Once, add a www.dev.example.com alias to the site so you have a preview URL.
  2. Once, if you want Twig debugging and a cache-free preview on Drupal 8+, drop in development.services.yml and run drush @example.com cr.
  3. Edit your theme or module code in place from your shell or over SFTP.
  4. Refresh https://www.dev.example.com to see the change with full errors and template hints. For a config or Twig change that isn't showing, drush @example.com cr (or cc all on Drupal 7) and refresh again.
  5. When you're happy, the live URL already has your code — it's the same site. Just take the development bits back off: remove development.services.yml (and clear caches), or set theme_debug 0 on Drupal 7.

If you ever want a throwaway copy to experiment on instead of your live site, clone the site first — see Cloning and migrating — and point a .dev. alias at the clone.

When something needs a change at the server level that these files and tasks can't reach — a PHP extension that isn't installed, a resource limit, anything outside your own instance — that's something your host does. Open a support request and describe what you need.

© 2026 BOA Documentation. All rights reserved.