Customization
About 714 wordsAbout 2 min
Customization
How to extend a Spora install with custom tools, agents, and recipes.
Custom tools
Tools are PHP classes implementing ToolInterface. Four ways to ship them:
1. As a plugin (recommended for reusable tools)
Scaffold from spora-ai/spora-plugin-skeleton (the plugin-author template):
# Use the GitHub "Use this template" button, then:
composer create-project spora-ai/spora-plugin-skeleton my-tool
# Or clone: git clone https://github.com/spora-ai/spora-plugin-skeleton my-toolEdit the generated tool class, commit, tag a release, push to GitHub. Then in your operator install:
composer require my-vendor/my-tool2. As an App extension (recommended for project-local code)
The Symfony-style approach: drop a class in app/App.php (shipped with this skeleton as a stub) and override any hook. The framework discovers the file via reflection — no manifest, no slug, no composer require.
// app/App.php
final class App extends \Spora\Extensions\AbstractExtension
{
public function getName(): string { return 'My Spora App'; }
public function tools(): array { return [Tools\MyTool::class]; }
}Status: the App extension surface is in progress — three open PRs are landing it. The skeleton’s composer.json currently pins the latest stable; the shipped app/App.php is a dormant stub — it does nothing until the framework upgrade ships. The phpstan.neon, composer lint, composer analyse scripts are wired up locally, so when you upgrade, the scaffold is ready to use.
When to migrate App → Plugin: when you need to ship the same code to multiple Spora installs. Renaming App.php → Plugin.php plus a plugin.json is the entire migration.
Full reference: see the Plugin authoring → App extension(Open in new window) page (migrated from spora-core/docs/08_app_extension.md).
3. In-app (for one-off tools)
Drop a PHP class into your project’s app/Tools/ (or any PSR-4 path you autoload), implement ToolInterface, and register it via ToolConfigService. Prefer the App extension above — this path is for the rare case where you don’t want a single App entry-point at all.
4. Fork the skeleton
If your customization is tightly coupled to the operator project, fork the skeleton, edit the files in place, and rebuild the Docker image.
Custom agents
Agents are Eloquent models. Create via the admin UI at /apps/agents, or programmatically:
$agent = new Agent([
'name' => 'Researcher',
'system_prompt' => 'You are a research assistant...',
'driver' => 'anthropic',
'model' => 'claude-sonnet-4-6',
]);
$agent->save();Custom recipes
Recipes are YAML files in recipes/. Schema: see the Architecture overview. Drop a new YAML file in, then refresh the recipe list (admin UI or CLI).
Custom mail templates
System-email defaults are YAML files under email-templates/. A project-local file with the same template name takes precedence over the default bundled with spora-core.
name: welcome
subject: 'Welcome to {{site_name}}'
body: |
Hi {{user_name}},
Your account is ready.
body_html: '<main>{markdown_html}</main>'bodyis CommonMark Markdown and is rendered to HTML and plain text.body_htmlis an optional trusted HTML shell. Include{markdown_html}where the rendered Markdown should be injected.{{variable}}placeholders are substituted at send time; unknown placeholders remain unchanged.
After changing YAML defaults, reconcile them with the database:
php bin/spora mail:templates:sync --check # dry run; exits 1 on drift
php bin/spora mail:templates:sync # prompt before overwriting each drifted row
php bin/spora mail:templates:sync --force # overwrite without promptingExisting installations must run php bin/spora spora:install first so the body_text column is renamed to body. After that, running php bin/spora db:seed will pick up any new mail-template YAMLs (it does not overwrite operator-customised rows). To overwrite drifted rows, run php bin/spora mail:templates:sync --force.
Theming the admin UI
The admin UI is a prebuilt Composer package. To customize:
Fork spora-ai/spora-frontend.
Modify the Vue components.
Build:
npm run build.Install your fork as a path repo:
composer require spora-ai/spora-frontend --path=../my-frontend-fork composer installCommit your fork + skeleton’s
composer.jsonupdates.
Environment variables
All configuration is .env-driven. See Environment variables for the full reference (every SPORA_* var with its default and its effect).