# Creating a theme

For developers

A GrandNode storefront theme is a plugin in the `Themes` group. It does not fork the storefront: it overrides only the Razor views it ships and every other view falls through to the defaults in `Grand.Web/Views`. The shipped `Theme.Modern` and `Theme.Nordic` are complete references; this article explains the mechanism and the files you need.

### The files

```
src/Plugins/Theme.Example/
  Theme.Example.csproj
  Manifest.cs               Group = "Themes", SystemName = "Theme.Example"
  ExampleThemePlugin.cs     BasePlugin
  ExampleThemeView.cs       IThemeView - the whole mechanism
  StartupApplication.cs     registers the IThemeView
  logo.jpg                  plugin list logo
  Content/                  CSS, JS, fonts, images, theme.jpg
  Views/Example/            the overridden views
    _ViewImports.cshtml
    _ViewStart.cshtml
    Shared/...
    Product/...
```

A theme without settings needs no install logic:

```
public class ExampleThemePlugin : BasePlugin, IPlugin;
```

And one registration:

```
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
    services.AddScoped<IThemeView, ExampleThemeView>();
}
```

### IThemeView and the fallback

```
public class ExampleThemeView : IThemeView
{
    public string AreaName => "";
    public string ThemeName => "Example";

    public ThemeInfo ThemeInfo => new("Example theme",
        "~/Plugins/Theme.Example/Content/theme.jpg", "A short description", false);

    public IEnumerable<string> GetViewLocations()
    {
        return new List<string> {
            "/Views/Example/{1}/{0}.cshtml",
            "/Views/Example/Shared/{0}.cshtml",
            "/Views/{1}/{0}.cshtml",
            "/Views/Shared/{0}.cshtml"
        };
    }
}
```

| Member | What it does |
|---|---|
| `AreaName` | Empty for storefront themes. |
| `ThemeName` | The key stored in settings. It must equal the folder name under `Views/`, because the location strings contain it. |
| `ThemeInfo` | Title, preview image, preview text and whether the theme supports right-to-left languages. The admin offers only RTL-capable themes for RTL stores. |
| `GetViewLocations()` | Ordered view locations; `{0}` is the view name, `{1}` the controller name. The first match wins. |

The last two locations are the fallback. With them, a request for `Product/ProductTemplate.Simple` uses `/Views/Example/Product/ProductTemplate.Simple.cshtml` if the theme has it and the default view otherwise. Remove them and every page the theme has not copied fails with "view not found".

### Overriding views

1. Copy `_ViewImports.cshtml` from an existing theme into `Views/Example/`. A theme folder does not inherit `Grand.Web`'s imports. Keep the line that removes the default input tag helper, otherwise every checkbox renders twice:
   ```
   @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.InputTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
   ```
2. Add `_ViewStart.cshtml`.
3. Copy the views you want to change from `src/Web/Grand.Web/Views` into the same relative path under `Views/Example/`. Start with layouts and shared partials — that is where the look of a theme lives. `Theme.Modern` overrides about 110 of the 235 default views, most of them in `Shared/`.
4. Keep the `@model`, route names and storefront data attributes (add to cart, wishlist, compare, quick view) of every view you copy, and keep its widget zones:
   ```
   @await Component.InvokeAsync("Widget", new { widgetZone = "productdetails_top" })
   ```
   Dropping a zone silently disables every widget plugin on that page.

A theme never changes view models, controllers or routes. If a theme needs data the storefront does not provide, the change belongs in `Grand.Web`.

### Content assets

CSS, JavaScript, fonts and images go under `Content/` and are referenced as `~/Plugins/Theme.Example/Content/...`. Mark them to be copied in the project file, and do not load them from external CDNs:

```
<ItemGroup>
  <None Update="Content\**\*.*"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></None>
  <None Update="logo.jpg"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></None>
</ItemGroup>
```

A theme also references `Grand.Web` to compile against the storefront view models, with `<Private>false</Private>` and `<ExcludeAssets>all</ExcludeAssets>` so the host assembly is not copied. The rest of the project file follows [Writing a plugin](https://grandnode.com/developers-writing-a-plugin): Razor SDK, `AddRazorSupportForMvc`, `StaticWebAssetsEnabled=false` and an output path to `Grand.Web/Plugins/Theme.Example` for both Debug and Release.

Prefer CSS over copying a view. Every copied view has to be compared with its upstream version on each GrandNode upgrade; a stylesheet rule does not.

### Activating the theme

1. Build the theme and install it under **Plugins → Local plugins** (group *Themes*).
2. Open **Settings → General** and choose the theme in **Default store theme**. The setting is store-scoped, so each store can use a different theme.
3. Optionally enable **Allow customers to select a theme**. The customer's choice is kept in a cookie and wins over the default.

Razor views of a theme are compiled into its DLL: after editing a view, rebuild the theme and restart the site.

### Tips and common mistakes

| Symptom | Cause |
|---|---|
| Theme selected but pages show the default markup | `ThemeName` does not match the `Views/` folder, or the `IThemeView` is not registered. |
| Some pages fail with "view not found" | The fallback locations are missing from `GetViewLocations()`. |
| Tag helpers are not resolved in theme views | No `_ViewImports.cshtml` in the theme folder. |
| Widgets disappear on one page | A widget zone was dropped from a copied view. |
| Theme CSS returns 404 | `Content/**` is not copied to the output. |

### Related

- [Writing a plugin](https://grandnode.com/developers-writing-a-plugin)
- [Plugin provider types](https://grandnode.com/developers-plugin-provider-types) — widgets add markup to zones without a theme.

- [Web page](https://grandnode.com/developers-creating-a-theme)
