This document is the authoritative reference for:
- How InvoiceShelf loads extension metadata
- The
extensions.jsonmanifest format - How to package a module ZIP that installs correctly
- How to publish a new version and list it in this catalog
For building modules inside Laravel, see the Module development guide in the InvoiceShelf repository (nwidart/laravel-modules, Modules/, admin assets, menus). For general setup, see the developer guide.
PDF templates (invoice & estimate Blade packs) use a separate manifest: TEMPLATES.md and root templates.json (same Admin → Modules installer).
- How the catalog is consumed
- Manifest file structure
- Field reference
- Slug and module name
- Compatibility with InvoiceShelf versions
- Download URL and releases
- ZIP package layout
- End-to-end publishing workflow
- PR checklist
- Self-hosted manifest
- Security and expectations
- Troubleshooting
InvoiceShelf reads a single JSON document from a configurable URL.
- Default URL:
https://raw.githubusercontent.com/InvoiceShelf/extensions/main/extensions.json - Override (self-hosted): set
INVOICESHELF_EXTENSIONS_MANIFEST_URLin the InvoiceShelf.envto your own HTTPS URL returning the same JSON shape.
The application:
- Fetches JSON over HTTP(S) with a short timeout.
- Normalizes the payload (supports either
{ "extensions": [ ... ] }or a bare array). - Maps each row to the in-app store (name, description, install state, update availability).
- On install, downloads the ZIP from
download_url, extracts it underModules/, runs migrations and seeds, and enables the module.
If the manifest cannot be loaded, the UI may show an extensions catalog unavailable state.
flowchart LR
subgraph catalog [This repo]
JSON[extensions.json]
end
subgraph app [InvoiceShelf]
Fetch[HTTP GET manifest]
Store[Modules UI]
DL[Download ZIP]
FS[Modules/]
end
JSON --> Fetch
Fetch --> Store
Store --> DL
DL --> FS
The canonical shape is a JSON object with an extensions array:
{
"extensions": [
{
"slug": "example",
"module_name": "Example",
"name": "Example",
"description": "Short description for the store listing.",
"version": "1.0.0",
"author": "Your Name",
"license": "MIT",
"tags": ["utility"],
"compatibility": {
"min_version": "1.3.0",
"max_version": "99.0.0"
},
"repository": "https://github.com/you/module-example",
"download_url": "https://github.com/you/module-example/releases/download/v1.0.0/Example.zip",
"cover": "https://raw.githubusercontent.com/you/module-example/main/preview.png"
}
]
}- Use UTF-8 encoding.
- Prefer 2-space indentation to match existing files.
- Keep
extensionsas a JSON array (ordered list); duplicates byslugare confusing—use a single entry per extension.
| Field | Required | Type | Description |
|---|---|---|---|
slug |
Recommended | string | Stable URL-safe identifier (lowercase, hyphens). Used as the store id. If omitted, the app may derive a slug from repository or name. Set explicitly to avoid accidental renames. |
module_name |
Recommended | string | Laravel module name as registered by nwidart/laravel-modules (typically StudlyCase, e.g. Payments, WhiteLabel). Must match the name field inside the module’s module.json and the folder under Modules/<module_name>/. Used for install/update matching. |
name |
Yes | string | Human-readable title in the store. |
description |
Yes | string | Short summary (shown in listings). |
version |
Yes | string | Semantic version of the ZIP at download_url. Must match what you ship; install/update logic compares this to the installed version. |
author |
Recommended | string | Person or organization name. |
license |
Recommended | string | SPDX or common id (e.g. MIT, AGPL-3.0). |
tags |
Optional | string[] | Keywords for filtering/display. |
compatibility |
Recommended | object | See Compatibility. |
compatibility.min_version |
Optional | string | Minimum InvoiceShelf app version (inclusive). |
compatibility.max_version |
Optional | string | Maximum InvoiceShelf app version (inclusive). |
repository |
Recommended | string | Public Git URL (source, issues, license). |
download_url |
Yes | string | Direct HTTPS link to a .zip file. Must not require login; redirects are followed. |
cover |
Optional | string | HTTPS URL to a preview image (e.g. PNG/JPG) for the store card. |
Slug
- Use kebab-case in the catalog (
payments,white-label). - Stable over time; changing slug effectively creates a “new” extension in the UI.
Module name
- Must match the nwidart module: directory
Modules/<module_name>/andmodule.json→"name": "<module_name>". - Examples from the official catalog:
Payments,WhiteLabel. - The installer resolves the downloaded package and expects
module.jsonto declare the samenameasmodule_namein the manifest.
If module_name is omitted in JSON, the consumer may derive it from the slug (StudlyCase). Prefer setting module_name explicitly to avoid ambiguity.
The app compares the running InvoiceShelf version (from settings or version.md) against compatibility.min_version and compatibility.max_version using PHP’s version_compare.
- Set
min_versionto the oldest InvoiceShelf release your extension supports. - Set
max_versionhigh enough for future lines (e.g.99.0.0) unless you know a breaking upper bound. - Update availability in the UI also respects compatibility: users outside the range may not be offered an update even if a newer ZIP exists.
Requirements
- HTTPS URL.
- Direct download of a ZIP (GitHub Release assets are ideal).
- Immutable per version — each catalog
versionshould point to a fixed URL for that artifact; avoid “latest.zip” that changes content without a version bump. - HTTP 200 for anonymous
GET(no API keys in query strings for public installs).
Suggested pattern (GitHub Releases)
- Tag:
v1.0.0 - Asset:
MyModule.zip - URL shape:
https://github.com/<org>/<repo>/releases/download/v1.0.0/MyModule.zip
Keep version in extensions.json identical to the module version you released and to the ZIP contents.
InvoiceShelf downloads the archive and copies extracted files into Modules/. The installer supports several layouts; the recommended one is:
Modules/
<ModuleName>/
module.json
composer.json
... (Providers, Routes, Database, Resources, etc.)
module.json must include a "name" field equal to module_name in the catalog.
Also supported (normalized automatically):
- Nested folder:
Modules/<ModuleName>/<ModuleName>/module.json— contents are promoted. - Single module at zip root with
module.jsondeclaring the correctname— moved intoModules/<ModuleName>/.
Avoid
- Zips that contain multiple unrelated top-level folders without a clear
module.json. - Missing
module.json— installation will fail.
After extraction, the app runs module:migrate, module:seed, and module:enable for your module name.
- Develop your extension as a Laravel module compatible with InvoiceShelf’s stack (PHP version, Laravel, Vue integration as needed).
- Tag a release in your repository and attach
MyModule.zipbuilt from the correctModules/<ModuleName>/tree. - Test install on a staging InvoiceShelf instance (upload or manual unzip under
Modules/if needed). - Fork InvoiceShelf/extensions.
- Edit
extensions.json: add or update your entry; setversion,download_url,compatibility. - Validate JSON and URLs (
curl -fIL <download_url>). - Open a pull request to
main. - After merge, the default manifest URL updates within seconds (CDN/cache may rarely delay).
Use this before submitting:
-
extensions.jsonis valid JSON. - New
slugdoes not duplicate an existing one (unless updating the same extension). -
versionmatches the Git tag / release asset. -
download_urldownloads a ZIP that installs cleanly on a fresh InvoiceShelf instance. -
module_namematchesmodule.jsoninside the ZIP. -
compatibilityreflects tested InvoiceShelf versions. -
repositoryandlicenseare correct. -
cover(if any) is HTTPS and publicly readable.
Organizations can host their own manifest:
- Host a JSON file (same schema as
extensions.json) on HTTPS. - Set
INVOICESHELF_EXTENSIONS_MANIFEST_URLin InvoiceShelf.envto that URL. - Optionally combine private entries with upstream metadata by proxying/merging JSON (operationally outside this repo).
An empty env value falls back to the default GitHub raw URL (see config/invoiceshelf.php in the main app).
- Extensions run with the same privileges as the InvoiceShelf application. Only list code you trust; reviewers cannot fully audit third-party binaries in every release.
- Do not submit manifest entries that point to malware, miners, or undisclosed data exfiltration.
- Prefer open-source repositories so users and maintainers can review changes.
- Report security issues through the repository linked in your entry or through InvoiceShelf’s security policy if the issue is in core integration.
| Symptom | Things to verify |
|---|---|
| Extension not listed | Manifest fetch errors; custom INVOICESHELF_EXTENSIONS_MANIFEST_URL; JSON syntax. |
| Install fails | ZIP layout; module.json name vs module_name; PHP errors in storage/logs. |
| Wrong version detected | version in catalog vs installed module record; mismatch with release tag. |
| No update offered | version not bumped; compatibility excludes current InvoiceShelf version; installed version already ≥ catalog version. |
- Blank template:
docs/examples/extension-entry.template.json - Live catalog:
extensions.jsonin this repository (official modules such as Payments and White Label).