eCommerce Platform for Small Business https://www.easel.engineering/
  • Go 92.8%
  • templ 5.9%
  • CSS 1%
  • HTML 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-20 08:58:23 +01:00
admin add merchant user erasure to the platform admin console 2026-09-20 08:58:23 +01:00
assets allow admin users to upload a favicon for their store 2026-09-20 08:58:23 +01:00
cmd speed up easel, export, and sqlite tests 2026-09-20 08:58:23 +01:00
internal fix seed data after customer erasure schema change 2026-09-20 08:58:23 +01:00
scripts split admin and store css into their own directories 2026-09-20 08:58:23 +01:00
.envrc change easel env and secrets handling 2026-09-20 08:58:23 +01:00
.gitignore change easel env and secrets handling 2026-09-20 08:58:23 +01:00
.golangci.yml initial commit 2025-12-24 17:02:43 -08:00
CONTRIBUTING.md fix seed data after customer erasure schema change 2026-09-20 08:58:23 +01:00
env_easel.ini.example re-add store data exports 2026-09-20 08:58:23 +01:00
env_easel_admin.ini.example prefix admin console env vars with easel_admin_ 2026-09-20 08:58:23 +01:00
flake.lock update dependencies 2026-09-12 10:20:28 +01:00
flake.nix update dependencies 2026-09-12 10:20:28 +01:00
go.mod add goleak to every test package 2026-09-20 08:58:23 +01:00
go.sum upgrade sqlite dependency, bound readers 2026-09-20 08:58:23 +01:00
LICENSE initial commit 2025-12-24 17:02:43 -08:00
Makefile enforce utc times with timelint 2026-09-20 08:58:23 +01:00
NOTICE relicense as agpl-3.0-or-later 2026-09-20 08:58:22 +01:00
README.md prefix admin console env vars with easel_admin_ 2026-09-20 08:58:23 +01:00
sqlc.json initial commit 2025-12-24 17:02:43 -08:00

Easel

Easel is a simple, self-hosted store for solo merchants. It gives you a storefront, a product catalog with variants, guest checkout, and an admin panel to manage orders, without the complexity of an enterprise retail platform.

Goals

  • Simple. Its setup, upgrades and maintenance should be automatic and easy to do for anyone comfortable with Linux. This means:
    • We scale vertically.
    • We use sqlite for the database.
    • We store images on disk.
  • Fast. Pages load instantly with lightweight footprints and optimized images.
    • We use Go with server-side templates.
    • No invasive analytics or tracking.
    • It works in pure HTML. JS is optional.
  • Secure. It should be an appropriate safeguard of your store's financial data.
    • Evan built pentested, SOC2 and PCI audited systems in the past with millions of customers and is applying those same lessons and approaches here.

Support

If you'd like to support continued development of Easel, you can purchase premium hosting at https://www.easel.engineering/.

Premium hosting gets you:

  • PCI compliance
  • Automated backups and recovery
  • Performance optimizations
  • Data migration services
  • Evan's direct phone number for support

Premium customers have a significant influence on the product roadmap and priorities.

Configuration

Easel reads all configuration from environment variables and parses it into native Go types at startup. Each service has one env file that holds both its public settings and its secrets, so there is nothing to keep in sync across separate files.

  • env_easel.ini — the server. Template: env_easel.ini.example.
  • env_easel_admin.ini — the admin console. Template: env_easel_admin.ini.example.

The server's variables all use the EASEL_ prefix and the console's all use the EASEL_ADMIN_ prefix, so the two never clash even when both files are loaded into one shell. Each service also has its own base URL variable (EASEL_BASE_URL for the server, EASEL_ADMIN_BASE_URL for the console) so the two can be set independently. Values that must match, such as the SMTP and Stripe credentials, are named separately in each file but must be set to the same value.

The binary never reads the file itself. Pass it to your service manager, for example with systemd:

# easel.service
[Service]
EnvironmentFile=/etc/easel/env_easel.ini
ExecStart=/usr/local/bin/easel
# easel-admin.service
[Service]
EnvironmentFile=/etc/easel/env_easel_admin.ini
ExecStart=/usr/local/bin/easel-admin

At deploy time, append the secrets to each service's file and place it under /etc/easel/.

The app always reads os.Getenv, so the same mechanism works whether the variables come from systemd, a container, or a shell. Invalid values are reported all at once at startup, before the server binds, so a typo fails fast instead of silently falling back to a default.

Local Development

To get dependencies, first install nix, configure the flakes experimental feature, and enter a nix devShell in this directory. Then run make deps to retrieve all the remaining Go dependencies.

Copy the templates and fill in the secrets:

cp env_easel.ini.example env_easel.ini
cp env_easel_admin.ini.example env_easel_admin.ini

.envrc loads both files with direnv, so make up, make admin, make migrate, and make seed pick them up automatically. Keep shared values (SMTP, Stripe, database) identical in the two files so the server and console operate on the same store. Generate secret values with ./scripts/generate_internal_api_secret.sh and ./scripts/generate_stripe_key_encryption_key.sh. EASEL_INTERNAL_API_SECRET is required for the server to start.

Tests

The test suite needs no configuration and runs offline by default. Two things read the environment:

  • A 32-byte EASEL_STRIPE_KEY_ENCRYPTION_KEY for the payment key setup tests. Tests supply a default when it is unset.
  • The tests that talk to the real Stripe test API. They run when EASEL_STRIPE_SECRET is set and skip otherwise. See "Stripe Integration Tests" in CONTRIBUTING.md for the permissions the key needs.

Stripe Configuration

Easel uses Stripe for payment processing. When running in platform mode (hosting multiple stores), each store provides their own Stripe API keys.

Supported Key Types

Stores can use either:

  • Standard secret keys (sk_live_* or sk_test_*)
  • Restricted API keys (rk_live_* or rk_test_*)

Restricted keys are recommended for security, as they limit API access to only what's needed.

Required Permissions for Restricted Keys

Easel uses two kinds of Stripe keys, and a restricted key needs different permissions depending on its role.

Store keys process a store's checkout and refunds:

Resource Permission
Payment Intents Write
Refunds Write

Stripe grants read access wherever it grants write access, so Easel validates a new key with read-only requests to the Payment Intents and Refunds endpoints before saving it. Only one record is requested and nothing is stored.

The platform key in the Easel config bills hosted subscriptions. A self-hosted single store that does not use subscriptions only needs the store permissions above:

Resource Permission
Payment Intents Write
Customers Read and Write
Payment Methods Read and Write

A standard secret key (sk_*) already has all of these permissions. Use a restricted key when you want to limit what Easel can access.

Webhook Events

Easel creates orders from Stripe webhooks, so each Stripe account needs a webhook endpoint pointing at the store's /stripe/webhook/ URL, such as https://store.example.com/stripe/webhook/. Subscribe the endpoint to:

Event Purpose
checkout.session.completed Create the order after a Checkout payment succeeds.
checkout.session.async_payment_succeeded Create the order when a delayed payment method settles.
payment_intent.succeeded Recover a paid order when embedded checkout confirms the card but the browser closes before the order is created.

Easel acknowledges every other event without acting on it, so extra events are harmless. It logs, but does not otherwise process, the refund events below, which are useful for monitoring:

  • refund.created
  • refund.updated
  • charge.refunded
  • charge.refund.updated

Copy the endpoint's signing secret (whsec_*) into EASEL_STRIPE_WEBHOOK_SECRET for a self-hosted store, or into the webhook field on the payment keys page for a hosted store.

Code Organization

We use a clean architecture for organization because it's quite a large codebase and we expect it to grow substantially over time.

Think of it like an onion with dependencies only flowing in one direction.

  • Domain defines our core types with no dependencies. Everything can import domain. This is the center of the onion.
  • Application holds our business logic. It can import domain.
  • Presentation defines the views, such as HTML pages and i18n translations. It can import application and domain.
  • Infrastructure is the outermost layer, and it interfaces with the outside world, including with HTTP, SMTP, the database, and external services like Stripe. This can import presentation, application, and domain.

The cmd/easel/main.go file ties these layers together, initializing concrete implementations for all the interfaces defined in the other packages.

Nix

We use Nix to provide a perfectly consistent development environment to all developers without the overhead of VMs or containers.

We recommend using Nix with direnv, so your environment is automatically configured for you by entering the directory.

You'll need to enable the flakes experimental feature in Nix.

Security

If you self-host Easel, you must use HSTS to force connections through HTTPS in the browser.

License

Easel is licensed under the GNU Affero General Public License, version 3 or later (AGPL-3.0-or-later). See the LICENSE file for details, and NOTICE for third-party attributions.

The internal/migrate/ directory contains code from thankful-ai/migrate licensed under the ISC license. See internal/migrate/LICENSE for details.