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.
php artisan inno:publish-themeIn 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
| Directory | Required | Description |
|---|---|---|
views/layouts/ | Yes | Main layout template, required for page rendering |
assets/scss/ | No | SCSS source, compiled to public/css/ |
assets/js/ | No | JS source, compiled to public/js/ |
public/ | No | Static assets (images, compiled CSS/JS), referenced via theme_asset() |
config.json | No | Theme metadata, required for marketplace publishing |
setup/boot.php | No | Theme boot hooks for registering Hooks & Filters |
setup/seeder.php | No | Demo data seeder for one-click import in admin panel |
routes/front.php | No | Custom front-end routes |
lang/ | No | Theme 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:
{
"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
// 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
// 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 positionadd_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
// 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:
$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:
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:
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
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:
<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:
@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>
@endsection6. Configuration File (config.json)
config.json is the theme's configuration file containing metadata:
{
"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 namename— Multilingual theme name displayed in the admin paneldescription— Multilingual theme descriptionversion— Semantic version numbericon— Theme thumbnail path (relative topublic/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:
# Compile a specific theme
THEME=factory npm run build
# Compile panel module + theme together
TARGET=panel THEME=factory npm run buildFor 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 JSUsing in Layout Files
Use theme_asset() to replace original asset references. The function automatically checks whether the theme file exists:
{{-- 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
Recommended Approach: Import Defaults First, Then Override with Customizations
This is the simplest and most efficient development approach.
CSS Development
// themes/factory/css/app.scss
@import '@front/css/app';
@import './page-product'; // Custom stylesJS Development
// 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:
- Clean the compilation directory
- Not compile any theme files
theme_asset()automatically falls back to default assets
Scenario 2: Customize Styles Only
Create a theme style file themes/factory/css/app.scss:
// Import default styles
@import '../../../innopacks/front/resources/css/app.scss';
// Custom styles
.product-title {
color: red !important;
}The system will:
- Clean the compilation directory
- Compile
app.scssto the theme directory 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 templateDevelopment Workflow
1. Create Theme Directory
mkdir -p themes/factory/css
mkdir -p themes/factory/js
mkdir -p themes/factory/views2. Copy Default Layout Files
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:
{{-- 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>4. Create Custom Styles (Recommended Approach)
// themes/factory/css/app.scss
@import '@front/css/app';
@import './page-product'; // Only add needed custom styles5. Compile Assets
THEME=factory npm run dev6. 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 orderbase/- Base layer (variables, globals, fonts)variables.scss- Theme variables (uses!default, allowing theme overrides)global.scss- Global common styles/utility classesfont.scss- Font-related styles
layout/- Layout layer (Header/Footer, etc.)header.scssfooter.scss
components/- Small components (typically reused across pages)breadcrumb.scssfilter-sidebar.scss
modules/- Functional modules (reusable business modules across pages)swiper.scssproduct-item.scssproduct-tab.scssnews.scssproduct-review.scsssocial.scss
pages/- Page layer (only applies to specific pages)home.scsslogin.scssaccount.scsscategories.scssproduct.scsscart.scsscheckout.scssorder.scssaddresses.scssbrands.scsscheckout-success.scsswallet.scsstransaction.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):
base/variables→base/global→base/fontlayout/*components/*modules/*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).
@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:
@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.
@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.scssuses!default, so$primary/$price/$defaultdefined 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:
@front→innopacks/front/resources
Example:
@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 viamodules/<module>.scss
Supported File Types
CSS/SCSS Files
app.scss- Main style filebootstrap/bootstrap.scss- Bootstrap stylespage-*.scss- Page-specific styles
JavaScript Files
app.js- Main script filecustom.js- Custom script file
Best Practices
- Use the recommended approach - Import defaults first, then override with customizations
- Keep it simple - Only create the custom files you need
- Version control - Use Git to manage theme files
- Test thoroughly - Test the theme under different scenarios
- Document well - Add comments for custom functionality
Notes
- Use the
THEMEenvironment variable to specify the build target, e.g.THEME=factory npm run build - Using the "import defaults first, then override with customizations" approach is recommended
- Using version control to manage theme files is recommended
- Each compilation cleans old files, ensuring the build output is clean
- 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