Self-hosted home inventory for everything you own.
Attic is an open-source home inventory application for individuals and households. Catalog everything you own—from appliances and tools to electronics, books, games, and furniture—and keep its location, condition, purchase information, warranty, photos, receipts, and manuals together.
Attic mirrors the way a home is organized with nested locations such as rooms, shelves, cupboards, and boxes. Categories and custom fields capture the right details for each kind of belonging, while collections group related items without changing where they are stored. It works for a focused collection or an entire household inventory.
Home Inventory
- Full CRUD with custom attributes per category (strings, numbers, booleans, dates, dropdowns)
- Hierarchical categories and locations; child categories inherit their ancestors' custom fields
- Condition tracking (new, used, damaged, or custom states)
- Warranty expiration monitoring on the dashboard
- File attachments for invoices, manuals, and photos
- Shared household collections with names, descriptions, and icons (e.g. PS5 games, books, furniture). Assign assets to any number of collections from the asset form and filter inventory by collection. Deleting a collection preserves its assets.
- Purchase dates, prices, and notes with dashboard value summaries
Search & Discovery
- Full-text search across asset names and descriptions
- Filter by category, location, and condition
- Category filters include assets assigned to descendant categories
- Search attribute values with case-insensitive substrings, including dropdown option labels
- Build nested Match all (AND) / Match any (OR) filters with typed attribute comparisons and collection membership
- Save named filters privately for reuse, and pin up to five as one-click desktop sidebar shortcuts; even workspace administrators cannot access another user's saved filters
Smart Integrations
- Automated imports from Google Books, TMDB (movies and TV), BoardGameGeek, IGDB (video games), and Rebrickable (LEGO sets and minifigures)
- Metadata and cover images populated automatically
- Plugin system for adding new import sources
Google Books works without credentials, but Google may apply a low shared quota to unauthenticated
requests. Set ATTIC_GOOGLE_BOOKS_API_KEY to a Google Books API key for reliable imports in a
self-hosted or shared deployment. The key is sent only to Google Books API requests.
IGDB imports require ATTIC_IGDB_CLIENT_ID and ATTIC_IGDB_CLIENT_SECRET from a
Twitch application. Attic exchanges these credentials for a
short-lived access token and keeps the token in memory only.
Rebrickable imports require ATTIC_REBRICKABLE_API_KEY. Create a free account and generate
an API key at Rebrickable, add it to your server environment
(or .env for Docker Compose), and restart Attic. The same key enables Rebrickable LEGO Sets
and Rebrickable Minifigures, each with its own category. Search by name or the complete
catalog number, including set variants (for example, 75192-1 or fig-000001). Each selection
creates one asset with quantity one and imports its image, catalog number, piece count, source
URL, and release year for sets when available. Both sources share a one-request-per-second
catalog limit and honor throttle cooldowns without automatic retries. Account synchronization,
bulk imports, part inventories, and importing a set's included minifigures are outside this workflow.
Self-Hosted & Secure
- Docker-based deployment with complete data ownership
- Local password authentication and OIDC/SSO (Keycloak compatible)
- REST API with Swagger documentation
- Local or S3-compatible storage for attachments
- Dark mode with mobile-responsive UI
Use Search attribute values on Assets for one search term, or open Advanced filter to combine rules and nested groups. Text supports equals/contains; numbers and dates support comparisons and inclusive ranges; booleans support true/false; dropdowns support any selected option, and multiple selections and collections also support all. Every attribute supports empty/not-empty checks. Missing values, null, empty strings, and empty arrays count as empty; zero and false do not.
Quick search matches values and dropdown labels, not attribute names or internal option
identifiers. %, _, and backslashes are literal characters. Only active, visible
attributes participate. Quick search, existing page filters, and advanced rules combine
with AND. Apply a draft immediately, save it as a new named filter, or explicitly update
an existing saved filter. Saved criteria exclude pagination. Deleted references or
incompatible attribute changes require repairing the affected rules before applying;
renames preserve references through stable IDs.
The API exposes GET /api/assets?attribute_q=Commodore, structured searches through
POST /api/assets/search, and personal CRUD under /api/saved-filters. A structured
search body is:
{
"criteria": {
"version": 1,
"expression": {
"kind": "group",
"match": "all",
"children": [
{ "kind": "rule", "field": "attribute_q", "operator": "contains", "value": "Commodore" },
{ "kind": "rule", "field": "q", "operator": "search", "value": "computer" }
]
}
},
"limit": 20,
"offset": 0
}Create a saved filter with {"name":"Retro computers","criteria":{...}}; update with
PUT /api/saved-filters/{id} using the same shape. Omitting criteria on update only
renames it, including when its rules need repair. GET/list responses include issues
with rule paths for invalid saved definitions. Expressions allow five group levels,
50 total rules, and 100 values per membership rule; JSON request bodies are limited to
64 KiB. See the bundled OpenAPI document at /api/docs for the complete wire format.
- Docker & Docker Compose
- mise to install Go, Bun, and Node.js from
mise.toml - Make
-
Clone the repository and start infrastructure:
git clone git@github.com:lmmendes/attic.git cd attic mise install mise run frontend:install docker compose up -d -
Run database migrations:
make migrate-up
-
Start the backend:
cd backend mise exec -- go run ./cmd/server
-
Start the frontend (new terminal):
cd frontend mise exec -- bun run dev
-
Open the app:
Service URL Frontend http://localhost:3000 Backend API http://localhost:8080 API Docs http://localhost:8080/api/docs Keycloak http://localhost:8180 Default test credentials:
testuser/testpassword
Run mise run typescript -- --version to check the project-local TypeScript 7
compiler, or pass compiler arguments after --. Its version is locked in the
frontend dependency tree rather than installed as a second global tool.
mise run frontend:check verifies the compiler, runs ESLint, and checks Vue types.
GitHub Actions installs Node.js, Bun, and Go with their setup actions and runs
the frontend package scripts directly. mise is used for local development.
TypeScript 7 is installed under the @typescript/native package alias and provides
tsc. The typescript import is aliased to Microsoft's @typescript/typescript6
compatibility package because ESLint and the current vue-tsc require the
JavaScript compiler API. Vue type checking continues through nuxt typecheck
using that API; installing the native compiler does not replace Vue's checker.
See Microsoft's side-by-side setup.
The Playwright suite exercises real browser workflows against a running Attic instance, including login, category/attribute handoffs, and feature settings.
cd frontend
mise exec -- bunx playwright install chromium
mise exec -- bun run test:e2eThe local suite targets the Nuxt development server at http://127.0.0.1:3000.
Set E2E_BASE_URL when testing another running instance.
GitHub Actions runs the same suite against the compiled application and a disposable PostgreSQL database on every pull request.
cp .env.example .env
# Edit .env with your production values
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml exec backend \
/app/migrate -path /migrations -database "$DATABASE_URL" upFor more details, visit getattic.dev.
| Component | Technology |
|---|---|
| Backend | Go 1.27, Chi router, PostgreSQL |
| Frontend | Nuxt 4, Nuxt UI 4, Tailwind CSS |
| Auth | Local passwords or OIDC/SSO |
| Storage | S3-compatible (AWS S3, MinIO, LocalStack) |
Open Profile → API keys from your account menu to create a key with a description, selected permissions, and optional expiry. Save the secret when it appears: Attic displays it only once and stores its SHA-256 hash. You can edit its description or revoke it; changing permissions or expiry requires a replacement key. The page lists active keys with their last-used time and a delete action. Administrators see only each person’s active-key count and the most recent use of those active keys in the People list. Key details and deletion remain private to the key owner.
Each resource offers no access, read, or read-write access. REST resource names are assets, locations, collections, categories, attributes, tags, conditions, warranties, attachments, asset_events, saved_filters, plugins, users, and organization. Omit resources with no access from the permissions object; use read or write for the others. Write includes read. Regular users cannot grant attribute-definition writes, user administration, or organization-setting writes. The creation form uses the role-specific catalog at GET /api/me/api-keys/permissions.
Authenticate REST requests with a bearer key:
curl -H "Authorization: Bearer $ATTIC_API_KEY" \
'https://attic.example.com/api/assets?limit=20&offset=0'
# POST searches require asset read access, not write access.
curl -H "Authorization: Bearer $ATTIC_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"criteria":{"version":1,"q":"camera"},"limit":20}' \
https://attic.example.com/api/assets/searchKeys are checked on every request against their current owner, role, workspace, revocation, expiry, and enabled features. Explicit invalid Authorization credentials fail even if a browser cookie is present or normal authentication is disabled. Key management, user creation, password changes, and password resets require interactive account authentication. Browser local and OIDC authentication continue to work.
Every member can find MCP availability and connection instructions in Profile → MCP connection. Local accounts can change their password under Profile → Account. Administrators enable MCP server in Settings → Integrations. It defaults to disabled. The independent status endpoint is GET /api/organization/mcp; admins update it with PUT /api/organization/mcp and {"enabled":true}. Disabling MCP immediately blocks subsequent MCP requests and leaves REST access available.
Connect an MCP client using stateless Streamable HTTP at /mcp, with the same bearer API key on every request. For clients that accept an mcpServers configuration with URL and headers:
{
"mcpServers": {
"attic": {
"url": "https://attic.example.com/mcp",
"headers": { "Authorization": "Bearer <your-api-key>" }
}
}
}MCP requires a key even when browser authentication is disabled. It supports named tools for asset listing/search/statistics and CRUD; locations, collections, categories, attributes and select options (including impact previews and confirmation); tags, conditions, warranties, attachments and main images; asset events; personal saved filters; import-plugin discovery/search/import; user listing/update/deletion; organization feature switches and MCP status. Authentication, sessions, credential operations, and key management are excluded. Imports require both plugin and asset write access. Tool discovery follows effective permissions and enabled features, and calls are authorized again before dispatch.
Tool arguments use the REST path parameter names (id, attachmentId, eventId, option_id, or pluginId), an optional query object with string values (an array for repeated tag_id), and a body matching the REST request. For example, get_asset takes {"id":"<asset-uuid>"} and create_asset takes {"body":{"name":"Camera","quantity":1}}. Results preserve REST status and response details as {"status":200,"body":...}; failed REST operations are marked as MCP tool errors. Linked summaries inside assets remain available with asset read permission.
upload_attachment takes the asset id and a body with filename, content_type, optional description, and base64 content. The decoded file limit is 50 MiB; MCP requests are bounded to 72 MiB. Attachment reads return metadata and the existing download URL. For confirmed attribute changes, pass the confirmation_token returned by preview_attribute_impact as the tool's top-level impact_token argument. Host and origin protections remain enabled. No OAuth or stdio transport is provided.
The authenticated operation registry in backend/internal/handler/operations.go defines REST dispatch, permissions, feature gates, and MCP inclusion together. Migration 34 adds keys and MCP status after the existing inventory and authentication migrations.
Open a location’s Photos tab to add multiple photos of boxes, shelves, rooms, and other storage spaces. Choose several files or drag them into the gallery. Uploads run sequentially; successful photos remain saved if another fails, and Retry failed photos retries only failed uploads. Photo management is separate from creating or editing a location.
Locations keep their icon until you choose Set as cover from a photo’s menu. Choose Remove cover to restore the icon without deleting the photo. Deleting the cover also restores the icon; other photos never become covers automatically. Each photo’s menu also offers Delete photo. Open a photo to view it full screen; use the arrow keys to browse and Escape to close.
Photos accept JPEG, PNG, or GIF files up to 10 MiB and 40 megapixels each, using the existing local or S3 file storage.
Location photos are stored at <uploads>/locations/<unique-id>/location-photo.<extension>, beneath the shared ATTIC_LOCAL_STORAGE_PATH. S3 uses the same locations/<unique-id>/location-photo.<extension> object-key layout in the shared bucket. Asset attachments and imported images keep their existing storage layout.
Deleting a photo preserves the location and its assets. Deleting a location also cleans up all its photos. Existing location photos are migrated into the gallery without moving their files. Rolling back migration 33 is refused while any location has multiple photos, to prevent data loss.
Gallery endpoints (restricted to the current organization and the locations feature):
GET /api/locations/{id}/photos: list cover first, then creation time and ID.POST /api/locations/{id}/photos: add one multipartfile; returns the created photo.PUT /api/locations/{id}/photos/{photoId}/cover: select the cover.DELETE /api/locations/{id}/photos/{photoId}/cover: unset this photo as cover without deleting it.DELETE /api/locations/{id}/photos/{photoId}: delete a photo.
Location responses include an optional photo_url for the cover. Gallery responses include each photo’s ID, filename, cover status, creation time, and URL. S3 URLs expire after 15 minutes; refresh the gallery or location data to renew them.