Skip to content

Theme Development Guide

Description

This document is intended to guide developers on how to develop and publish new themes for use with the InnoShop e-commerce system.

Publishing Themes

Use the following Artisan command to publish the theme in the innopacks/front directory as the default theme to /themes/default.

bash
php artisan inno:publish-theme

In the backend, under "Design - Template Theme List," select and activate the theme named default to easily switch to this theme.

Theme File Structure

You can create a new theme by renaming or copying the /themes/default folder.

Minimal Theme (only 2 directories required)

A working theme only needs views/layouts and a stylesheet:

themes/my-theme/
├── config.json                    # Theme metadata (required for marketplace)
├── assets/scss/app.scss           # Theme styles
└── views/
    └── layouts/
        └── app.blade.php          # Layout file (required)

Full Theme (with custom routes, demo data, i18n)

Using the GansuHub theme as an example:

themes/gansuhub/
├── config.json                    # Theme configuration
├── README.md                      # Theme documentation
├── setup/                         # Advanced: bootstrap & data
│   ├── boot.php                   # Theme boot hooks
│   └── seeder.php                 # Demo data seeder
├── routes/                        # Advanced: custom routes
│   └── front.php                  # Front-end routes
├── lang/                          # Advanced: multilingual
│   ├── en/
│   │   ├── common.php             # Common translations
│   │   ├── landing.php            # Homepage translations
│   │   ├── landport.php           # Custom page translations
│   │   └── ...
│   ├── zh-cn/
│   │   └── ...
│   └── ...
├── assets/                        # Frontend source assets
│   ├── scss/app.scss              # SCSS entry point
│   └── js/app.js                  # JS entry point
├── public/                        # Static assets (compiled/images)
│   ├── css/
│   ├── js/
│   └── images/
└── views/                         # Blade templates (required)
    ├── layouts/
    │   └── app.blade.php          # Main layout
    ├── home.blade.php             # Homepage
    ├── components/                # Reusable components
    │   ├── header.blade.php
    │   ├── footer.blade.php
    │   └── breadcrumb.blade.php
    ├── pages/                     # Custom pages
    │   ├── about.blade.php
    │   ├── contact.blade.php
    │   └── landport.blade.php
    ├── categories/
    ├── products/
    ├── account/
    └── shared/

Directory Reference

DirectoryRequiredDescription
views/layouts/YesMain layout template, required for page rendering
assets/scss/NoSCSS source, compiled to public/css/
assets/js/NoJS source, compiled to public/js/
public/NoStatic assets (images, compiled CSS/JS), referenced via theme_asset()
config.jsonNoTheme metadata, required for marketplace publishing
setup/boot.phpNoTheme boot hooks for registering Hooks & Filters
setup/seeder.phpNoDemo data seeder for one-click import in admin panel
routes/front.phpNoCustom front-end routes
lang/NoTheme multilingual files

If you need to revert changes to the theme template and restore the system's default template, you can do so by deleting the custom blade.php files in the views directory.

Once deleted, the system will automatically revert to using the original Blade templates in the innopacks/front directory for rendering.

1. CSS Styling

In the /css directory, you can organize multiple CSS or SCSS files to manage styles in a modular way.

2. JavaScript Scripts

The /js directory is used to store JavaScript files, which may include the theme's interactive logic and third-party libraries.

3. Public Static Resources

The /public directory is used to store the theme's static resources, which can be accessed via the web and are included in Blade templates with theme_asset().

4. Template Files

The /views directory contains the theme's Blade template files, which define the theme's layout and page structure.

5. Configuration File

config.json is the configuration file for the theme, shown in the following example:

json
{
    "code": "default",
    "name": {
        "zh_cn": "InnoShop Default Template",
        "en": "InnoShop Default Theme"
    },
    "description": {
        "zh_cn": "InnoShop Default Template",
        "en": "InnoShop Default Theme"
    },
    "version": "v1.0.0",
    "author": {
        "name": "InnoShop",
        "email": "team@innoshop.com"
    }
}

Advanced Features

1. Custom Routes (routes/front.php)

When a theme requires additional front-end pages (e.g., About Us, freight routes, infrastructure showcase), register custom routes via routes/front.php.

php
<?php
// themes/gansuhub/routes/front.php

use Illuminate\Support\Facades\Route;

Route::get('/landport', function () {
    return view('pages.landport');
})->name('pages.landport');

Route::get('/freight-routes', function () {
    return view('pages.freight-routes');
})->name('pages.freight_routes');

The corresponding view files are placed in the views/pages/ directory, e.g., views/pages/landport.blade.php.

Route files go in the routes/ directory. Currently front.php (front-end routes) is supported and auto-loaded by the system.

2. Theme Boot Hooks (setup/boot.php)

boot.php is loaded on every request via FrontServiceProvider. Use it to register hooks and filters.

php
<?php
// themes/gansuhub/setup/boot.php

return function () {
    // Insert custom HTML at the top of homepage content
    listen_blade_insert('home.content.top', function ($data) {
        return '<div class="custom-banner">...</div>';
    });

    // Register other hooks...
};

Common hook functions:

  • listen_blade_insert($hookName, $callback) — Insert HTML content at a specified position
  • add_filter($hookName, $callback) — Filter data

3. Demo Data Import (setup/seeder.php)

seeder.php is executed when importing demo data from the admin panel. It typically includes:

  • Clearing existing test data
  • Creating categories
  • Creating products
  • Creating articles
  • Creating pages
  • Setting homepage configuration (categories, hot products, slideshow, etc.)
  • Setting SEO meta information

Basic structure:

php
<?php
// themes/gansuhub/setup/seeder.php

use InnoShop\Common\Repositories\CategoryRepo;
use InnoShop\Common\Repositories\ProductRepo;
use InnoShop\Common\Repositories\PageRepo;
use InnoShop\Common\Repositories\SettingRepo;

return function (string $dir): void {
    $themeCode = basename($dir);

    // 1. Clear existing data
    clearExistingData();

    // 2. Create categories
    $categories = createCategories($themeCode);

    // 3. Create products
    $products = createProducts($categories, $themeCode);

    // 4. Create pages
    createPages();

    // 5. Setup homepage layout
    setupHomeSettings($categories, $products);

    // 6. Setup slideshow
    updateSlideshowSettings($themeCode);
};

Image path convention: Demo data images use the static/themes/{themeCode}/images/ prefix. During import, images from public/images/ are copied to public/static/themes/{themeCode}/images/.

Creating categories example:

php
$category = CategoryRepo::getInstance()->create([
    'slug'         => 'new-energy-vehicles',
    'position'     => 1,
    'active'       => 1,
    'image'        => 'static/themes/gansuhub/images/home/category-nev.webp',
    'translations' => [
        ['locale' => 'zh-cn', 'name' => '新能源汽车', 'summary' => 'Description...'],
        ['locale' => 'en', 'name' => 'New Energy Vehicles', 'summary' => 'Description...'],
    ],
]);

Setting homepage product floors:

php
SettingRepo::getInstance()->updateSystemValue('home_hot_products', [
    'display_mode' => 'flat',
    'title_align'  => 'center',
    'floors'       => [
        [
            'name'     => ['zh-cn' => 'Floor Name', 'en' => 'Floor Name'],
            'subtitle' => ['zh-cn' => 'Subtitle', 'en' => 'Subtitle'],
            'products' => [$productId1, $productId2],
        ],
    ],
]);

Setting slideshow:

php
SettingRepo::getInstance()->updateSystemValue('slideshow', [
    [
        'image'    => ['zh-cn' => $base.'slideshow/hero-01.webp', 'en' => $base.'slideshow/hero-01.webp'],
        'link'     => 'product:byd-atto3-export',  // Product link uses product:{slug}
        'title'    => ['zh-cn' => '标题', 'en' => 'Title'],
        'subtitle' => ['zh-cn' => '副标题', 'en' => 'Subtitle'],
    ],
]);

4. Theme Multilingual (lang/)

Themes support independent language files for custom page translations. Language files are placed in lang/{locale}/ and loaded in Blade templates via the theme_trans() function.

Directory structure:

lang/
├── en/
│   ├── common.php        # Global translations (nav, footer, etc.)
│   ├── landing.php       # Homepage translations
│   ├── landport.php      # Land port page translations
│   ├── freight.php       # Freight page translations
│   └── ...
├── zh-cn/
│   ├── common.php
│   └── ...
└── ru/
    └── ...

Language file example (lang/en/landport.php):

php
<?php
return [
    'hero_title' => 'Gansu (Lanzhou) International Land Port',
    'hero_subtitle' => 'A national Class-I land port...',
    'stat_1_val' => '4D6R',
    'stat_1_label' => 'International Logistics Corridors',
];

Usage in Blade:

blade
<h1>{{ theme_trans('landport.hero_title') }}</h1>
<p>{{ theme_trans('landport.hero_subtitle') }}</p>

theme_trans() automatically loads the file from lang/{locale}/ based on the current locale.

5. Custom Page Views

Custom page views are placed in the views/pages/ directory and accessed via routes registered in routes/front.php.

Page templates typically extend the main layout:

blade
@extends('layouts.app')
@section('title', theme_trans('landport.hero_title'))
@section('body-class', 'page-landport')

@section('content')

<x-front-breadcrumb type="static" value="" title="{{ theme_trans('landport.hero_title') }}" />

<section class="gh-landport-hero">
  <div class="gh-landport-hero__inner tf-container">
    <h1>{{ theme_trans('landport.hero_title') }}</h1>
    <p>{{ theme_trans('landport.hero_subtitle') }}</p>
  </div>
</section>

@endsection

6. Configuration File (config.json)

config.json is the theme's configuration file containing metadata:

json
{
    "code": "gansuhub",
    "name": {
        "zh-cn": "陇贸优选",
        "en": "GansuHub"
    },
    "description": {
        "zh-cn": "甘肃跨境电商平台",
        "en": "Gansu cross-border platform"
    },
    "version": "v1.0.0",
    "icon": "/images/logo.png",
    "author": {
        "name": "FunnLink",
        "email": "team@innoshop.com"
    }
}
  • code — Unique theme identifier, must match the directory name
  • name — Multilingual theme name displayed in the admin panel
  • description — Multilingual theme description
  • version — Semantic version number
  • icon — Theme thumbnail path (relative to public/ directory)
  • author — Author information

Simplified Asset Loading System

InnoShop supports a simplified theme asset loading system that allows theme developers to easily customize styles and scripts while keeping the system clean. The system uses an "import defaults first, then override with customizations" approach, implemented through the theme_asset() function's intelligent fallback mechanism.

How It Works

Compilation Logic

  • Only compile existing files: Theme SCSS/JS files are only compiled to the theme directory when they actually exist
  • Simple and straightforward: No complex smart-import logic; theme developers have full control over what gets imported
  • Auto-cleanup: The theme compilation directory is automatically deleted before each build, preventing stale file residue

Compilation Output

  • Theme CSS files → public/static/themes/{theme}/css/app.css
  • Theme JS files → public/static/themes/{theme}/js/app.js
  • Theme Bootstrap → public/static/themes/{theme}/css/bootstrap.css

Runtime Loading

  • Uses the theme_asset() function for intelligent asset loading
  • If the theme file exists, the theme file is loaded
  • If the theme file does not exist, it automatically falls back to the default file
  • No need to manually check file existence

Setup and Compilation

Compiling Custom Theme CSS and JS

InnoShop uses Vite for frontend asset building. Specify the theme to compile via the THEME environment variable:

shell
# Compile a specific theme
THEME=factory npm run build

# Compile panel module + theme together
TARGET=panel THEME=factory npm run build

For detailed frontend build instructions, see Frontend Build Guide.

The build process is driven by the build.js script, which compiles the theme's SCSS and JS entries individually and outputs to public/static/themes/{theme}/.

Compiled files are output to:

public/static/themes/factory/
├── css/
│   ├── app.css          # Compiled theme SCSS
│   └── bootstrap.css    # Compiled theme Bootstrap
└── js/
    └── app.js           # Compiled theme JS

Using in Layout Files

Use theme_asset() to replace original asset references. The function automatically checks whether the theme file exists:

blade
{{-- Smart theme asset loading - automatic fallback mechanism --}}
<link rel="stylesheet" href="{{ theme_asset('css/bootstrap.css') }}">
<link rel="stylesheet" href="{{ theme_asset('css/app.css') }}">
<script src="{{ theme_asset('js/app.js') }}"></script>
  • If the file exists → returns the theme file path
  • If the file does not exist → returns the default file path

Theme Development Modes

This is the simplest and most efficient development approach.

CSS Development

scss
// themes/factory/css/app.scss
@import '@front/css/app';
@import './page-product';  // Custom styles

JS Development

javascript
// themes/factory/js/app.js
import '@front/js/inno-bootstrap'
import { initInno, bindGlobalEvents } from '@front/js/inno-init'
import '@front/js/inno-validation'
import '@front/js/inno-header'
import '@front/js/inno-footer'
import '@front/js/inno-autocomplete'

initInno()

$(function () {
  bindGlobalEvents()
})

import './custom'

Advantages

  • Minimal code - Only 2 lines needed
  • Easy maintenance - Automatically follows default style updates
  • Perfect overrides - CSS cascade ensures custom styles take priority
  • Developer-friendly - Theme developers only focus on customizations

Theme Development Examples

Scenario 1: Use Default Assets Entirely

If the theme has no custom files, the system will:

  1. Clean the compilation directory
  2. Not compile any theme files
  3. theme_asset() automatically falls back to default assets

Scenario 2: Customize Styles Only

Create a theme style file themes/factory/css/app.scss:

scss
// Import default styles
@import '../../../innopacks/front/resources/css/app.scss';

// Custom styles
.product-title {
  color: red !important;
}

The system will:

  1. Clean the compilation directory
  2. Compile app.scss to the theme directory
  3. theme_asset() uses the theme CSS, default JS

Scenario 3: Fully Custom Theme

Create all required files:

themes/factory/
├── css/
│   ├── app.scss              # Main style file
│   ├── bootstrap/
│   │   └── bootstrap.scss    # Custom Bootstrap
│   └── page-product.scss     # Custom page styles
├── js/
│   ├── app.js                # Main script file
│   └── custom.js             # Custom scripts
└── views/
    ├── layouts/
    │   └── app.blade.php     # Layout file
    └── products/
        └── show.blade.php    # Custom page template

Development Workflow

1. Create Theme Directory

bash
mkdir -p themes/factory/css
mkdir -p themes/factory/js
mkdir -p themes/factory/views

2. Copy Default Layout Files

bash
cp innopacks/front/resources/views/layouts/app.blade.php themes/factory/views/layouts/

3. Modify Layout Files

Use theme_asset() to replace original asset references:

blade
{{-- Replace original mix() calls --}}
<link rel="stylesheet" href="{{ theme_asset('css/bootstrap.css') }}">
<link rel="stylesheet" href="{{ theme_asset('css/app.css') }}">
<script src="{{ theme_asset('js/app.js') }}"></script>
scss
// themes/factory/css/app.scss
@import '@front/css/app';
@import './page-product';  // Only add needed custom styles

5. Compile Assets

bash
THEME=factory npm run dev

6. Test Theme

Visit the storefront pages to verify the results.

Front SCSS Architecture

The front SCSS is organized into layers: base/layout/components/modules/pages. When reusing default styles in themes, it is recommended to use @front/css/app as the entry point.

Directory Structure

Path: innopacks/front/resources/css/

  • app.scss - Entry file, only responsible for organizing imports in order
  • base/ - Base layer (variables, globals, fonts)
    • variables.scss - Theme variables (uses !default, allowing theme overrides)
    • global.scss - Global common styles/utility classes
    • font.scss - Font-related styles
  • layout/ - Layout layer (Header/Footer, etc.)
    • header.scss
    • footer.scss
  • components/ - Small components (typically reused across pages)
    • breadcrumb.scss
    • filter-sidebar.scss
  • modules/ - Functional modules (reusable business modules across pages)
    • swiper.scss
    • product-item.scss
    • product-tab.scss
    • news.scss
    • product-review.scss
    • social.scss
  • pages/ - Page layer (only applies to specific pages)
    • home.scss
    • login.scss
    • account.scss
    • categories.scss
    • product.scss
    • cart.scss
    • checkout.scss
    • order.scss
    • addresses.scss
    • brands.scss
    • checkout-success.scss
    • wallet.scss
    • transaction.scss

app.scss Loading Order

The entry file innopacks/front/resources/css/app.scss is recommended to follow this order (group similar types together, with more "fundamental" layers first):

  1. base/variablesbase/globalbase/font
  2. layout/*
  3. components/*
  4. modules/*
  5. pages/*

If the default entry @front/css/app does not cover certain styles, you can import them individually on the theme side via @front/css/<group>/<file>.

How Custom Themes Reuse Default SCSS

Theme-side entry example: themes/<theme>/css/app.scss

Method 1: Import Entry (Recommended)

Use case: Most themes (want to fully reuse default styles, only write overrides/increments).

scss
@import './_variables';

@import '@front/css/app';

@import './global';
@import './header';
@import './footer';

If the theme needs to import a style that is not included in the default entry, you can append it as needed:

scss
@import '@front/css/<group>/<file>';

Method 2: Import Individual Files (Fine-grained)

Use case: Landing/lightweight themes, only overriding a few pages, or wanting to minimize CSS file size.

scss
@import './_variables';

@import '@front/css/base/variables';
@import '@front/css/base/global';
@import '@front/css/base/font';

@import '@front/css/layout/header';
@import '@front/css/layout/footer';

@import '@front/css/components/breadcrumb';
@import '@front/css/components/filter-sidebar';

@import '@front/css/modules/swiper';
@import '@front/css/modules/product-item';

@import '@front/css/pages/home';

@import './header';
@import './pages/home';

Two common strategies:

  • Override - Still import the default module/page styles of the same name, then append overriding styles on the theme side (place them after the default imports)
  • Replace - Do not import the default module/page styles at all; the theme fully implements those styles, avoiding interference from default styles

Key points:

  • The theme variables file must be imported before @front/css/app
  • The default base/variables.scss uses !default, so $primary/$price/$default defined by the theme first will not be overridden by the defaults
  • Additional capabilities can be imported as needed (from @front/css/<group>/<file>)

@front Alias Explanation

The build configuration provides aliases for SCSS/JS:

  • @frontinnopacks/front/resources

Example:

scss
@import '@front/css/app';

Migration and New File Rules

  • New files should be placed in the corresponding directory by purpose: base/layout/components/modules/pages
  • File names should use lowercase kebab-case: checkout-success.scss
  • If a module grows into multiple files: split them under modules/<module>/ and aggregate imports via modules/<module>.scss

Supported File Types

CSS/SCSS Files

  • app.scss - Main style file
  • bootstrap/bootstrap.scss - Bootstrap styles
  • page-*.scss - Page-specific styles

JavaScript Files

  • app.js - Main script file
  • custom.js - Custom script file

Best Practices

  1. Use the recommended approach - Import defaults first, then override with customizations
  2. Keep it simple - Only create the custom files you need
  3. Version control - Use Git to manage theme files
  4. Test thoroughly - Test the theme under different scenarios
  5. Document well - Add comments for custom functionality

Notes

  1. Use the THEME environment variable to specify the build target, e.g. THEME=factory npm run build
  2. Using the "import defaults first, then override with customizations" approach is recommended
  3. Using version control to manage theme files is recommended
  4. Each compilation cleans old files, ensuring the build output is clean
  5. The theme_asset() function automatically handles file existence checks

Troubleshooting

Issue 1: Compilation Fails

  • Check if the theme directory exists
  • Check if the SCSS syntax is correct
  • Check if the file paths are correct

Issue 2: Styles Not Taking Effect

  • Check if compilation succeeded
  • Check the browser cache
  • Check if theme_asset() is correctly falling back

Issue 3: Default Styles Being Overridden

  • Check if the theme file correctly imports the default files
  • Check if CSS priority is correct
  • Ensure custom styles are imported after default styles

Issue 4: File Not Found Error

  • Check if the theme_asset() function is working properly
  • Ensure default files exist
  • Check if the file paths are correct

Issue 5: Cleanup Fails

  • Check file permissions
  • Ensure no other processes are using the files
  • Manually delete the compilation directory and retry