Tungsten, the free Filament alternative for Python and FastAPI, now matches most Filament features: custom themes, richer forms, faster tables, nested modals, background exports, API tokens, live notifications, a full activity log and multi-tenancy.
In short: Tungsten is a free, open source Filament alternative for Python and FastAPI. Its latest update closes most of the gap with Laravel Filament: custom Tailwind themes, richer forms, faster tables, nested modals and wizards, background exports, two-factor codes by email, API tokens, live notifications, a full activity log, and teams with their own URLs (multi-tenancy). If you liked Filament but your team works in Python, you can now build the same kind of FastAPI admin panel without giving up features.
Tungsten (tungsten-admin) builds admin pages from Python classes. New to it? Start with our introduction to the Tungsten FastAPI admin panel. Below, each area of the update gets its own section with a short code example from the docs.
What is new in this Filament parity update?
We compared Tungsten with Filament feature by feature, listed every gap, and closed them in eight pull requests (#55 to #62), all merged on 14 October 2026. They cover themes, infolists and relation managers, notifications and the activity log, tables, actions and export, login and security, forms, and navigation and multi-tenancy.
You can follow every change on the Tungsten changelog.
Who is this Filament alternative for Python for?
- Laravel teams moving to Python. You know Filament and want the same way of working in FastAPI. Our guide to migrating Laravel Filament to Python and FastAPI shows a tool that converts your Filament code for you.
- Python and FastAPI developers who need an admin panel for a SaaS, a shop or an internal tool.
- Small businesses and agencies in India and worldwide: a shop with lakhs of orders (cursor pagination), a clinic that needs safe sign-in (email codes, idle timeout), or a school with courses and lessons (nested resources).
How do I install the update?
Everything in this post is in tungsten-admin 0.1.9, which is on PyPI now.
- Install or upgrade Tungsten:
pip install -U tungsten-admin - Some features add new tables (jobs, API tokens, email changes). Create them once after upgrading:
panel.create_tables(engine) - Turn on the features you want, as shown below.
The full code is on GitHub, and the installation guide covers a fresh project.
Can I build my own theme, like Filament's make:theme?
Yes. Before, Tungsten shipped a fixed Tailwind build, so a class like bg-lime-300 that Tungsten did not use itself did nothing. A custom theme is your own Tailwind build on top of Tungsten's, so every Tailwind class works. (Tailwind is the CSS tool Tungsten and Filament use for styling.)
tungsten make:themewritestheme.css,tailwind.config.js,package.jsonand.gitignoreintoadmin/theme/.tungsten theme:buildbuilds it, and--watchrebuilds on every change.- You need Node.js only on the machine that builds the theme. The server does not need it.
from pathlib import Path
panel = Panel(..., theme_stylesheet=Path(__file__).parent / "theme/dist/theme.css")
Read more in the theming docs and the CLI docs.
What is new in Tungsten forms?
- New fields:
ModalTableSelect(pick records from a searchable, paged table in a modal),Slider(one or two handles) andViewFieldfor your own template. The docs now show how to write a custom field by subclassingField. - Select can show a + button to create a new option in a modal, and a pencil to edit the selected one.
- Input masks:
mask("99999-99999")for phone numbers, andmoney_mask(prefix="₹", indian=True)for Indian digit grouping like ₹12,34,567.50. - Timezones:
DateTimePicker("starts_at").timezone("Asia/Kolkata")shows times in the user's zone and saves them in UTC. - Actions inside forms: buttons next to a hint, before or after an input, in an
Actionsrow or in a callout. They can fill other fields. - Layouts and rich text:
Flex(aliasSplit),Text,Html,Callout, tabs kept in the URL, and aRichEditorwith toolbar choice, attachments,@mentionsand custom blocks.
TextInput("phone").mask("99999-99999").strip_characters("-") # 98765-43210, saved as 9876543210
TextInput("price").money_mask(prefix="₹", indian=True) # ₹12,34,567.50, saved as Decimal("1234567.50")
See the form fields docs, form layouts docs and forms docs. If your team stores images in one place, the rich editor pairs well with our media library plugin for FastAPI admin.
How do Tungsten tables compare with Filament tables now?
- Filters in a pop-up or a dropdown:
layout="modal"orlayout="dropdown", as well as the side panel and the toolbar. - Apply button:
deferred_filters()runs filters only when the user presses Apply filters. This saves a database query per click on big tables. - Remember the table:
persist_in_session()brings back the user's last filters, search and sort. - Fast pagination:
mode="simple"shows Previous and Next with no total count.mode="cursor"starts each page after the last row of the one before, so page 5,000 is as fast as page 1. - Cards on phones:
stacked_on_mobile()turns rows into cards;content_grid({"md": 2, "xl": 3})shows a grid of cards on every screen. - Column groups and Range total:
ColumnGroup("Pricing", [...])puts one heading over several columns, andRange()shows the lowest and highest value in the footer.
table.paginated([25, 50], mode="cursor") # or table.cursor_pagination()
Read the tables docs, table filters docs and table features docs. Want users to keep named filter sets too? See our saved views plugin for FastAPI admin.
What can Tungsten actions do now?
- Modals on modals:
extra_modal_footer_actions()adds footer buttons. One with a form opens its own modal on top (up to two levels deep). - Chaining:
then()opens or runs another action after this one succeeds. - Wizard in a modal:
steps()turns the modal form into step-by-step pages. - Hooks and rate limits:
before()andafter()hooks, andrate_limit(3, per_seconds=60). Counts live in the server process memory. - Downloads:
DownloadActionsends or streams a file through a signed link that works for the same user for 5 minutes. - Big bulk actions:
chunk_selected_records(100)works in chunks, andbackground()runs the work after the response, then sends a bell notification.
from tungsten.forms.layout import Step
CreateAction().steps([
Step("Details").schema([TextInput("name").required()]),
Step("Price").schema([TextInput("price").numeric()]),
]).modal_submit_action_label("Create product")
Modal headers and footers are now sticky by default. See the actions docs.
Can big exports run in the background?
Yes. The user sees "Export started", keeps working, and gets a "Your export is ready" notification with a Download button.
ExportAction().background()always uses the background;min_rowssets a row count to start from.- Only the user who made the file can download it. Files are removed after 7 days.
- Every export is kept in the user's export history at
/admin/_tw/exports, with a "Download again" link.
ExportAction().background(min_rows=5000) # only when 5,000 or more rows match
See the import and export docs.
What is new in infolists and relation managers?
An infolist is a record's read-only view. A relation manager is the table of related records, like a team's projects.
- New entries:
CodeEntryshows code or JSON with colours and a copy button.ViewEntryrenders your own Jinja template. - Entry actions: any entry can carry buttons before, after, in the label row or under the value.
- Extra fields on the link table:
AttachAction().form([...])saves values like a member's role on the many-to-many table. - Associate and dissociate for one-to-many links, with
AssociateActionandDissociateAction. - Polymorphic and "has many through":
morph_many,morph_to_many, and dotted paths like"projects.tasks". - Own pages and nested resources:
separate_page = True, orrelated_resourcefor pages like/admin/courses/1/lessons/5/edit.
from tungsten.infolists import CodeEntry
CodeEntry("payload") # JSON column: pretty, coloured JSON
CodeEntry("script").language("python").line_numbers()
Read the infolists docs and the relation managers docs.
How secure is sign-in in this FastAPI admin panel?
- Two-factor codes by email:
Auth(two_factor_email=True), a 6-digit code that works once, for 10 minutes. - Verified email change: a new email works only after its owner opens a link. The old address gets a link to stop the change.
- Idle timeout:
Auth(idle_timeout=30)signs people out after 30 minutes without activity. Background polling does not count. - API tokens, like Laravel Sanctum: users make tokens with abilities and an expiry. Only a SHA-256 hash is stored.
- Security headers on every panel page: HSTS (over HTTPS), X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy and a Content Security Policy.
SecurityHeadersMiddlewarecan cover your whole site.
from fastapi import Depends
@app.get("/api/orders")
def list_orders(user=Depends(panel.api_user("orders:read"))):
... # user is the token's owner
See the authentication docs and security docs. To score your panel's security and lock out wrong passwords, read about our security audit, help desk, blog and SEO audit plugins. The impersonate user plugin lets admins see what a user sees, with a log.
What changed in notifications and the activity log?
- Email notifications:
send_to_mail(users, ctx)sends a notification through the panel's mailer, with an HTML version and full links. - Live notifications: each open tab keeps a WebSocket.
broadcast(users, ctx)pops a toast at once, and the bell count updates at once. - Full activity log: deletes, restores, every action run, exports, sign-ins, sign-outs, failed sign-ins and password resets, on top of creates and edits.
Notification("Ticket assigned").action("Open", url).send_to_database(agent, ctx).send_to_mail(agent, ctx)
Read the notifications docs and the new activity log docs.
What is new in navigation and multi-tenancy?
Tungsten now has Filament-style clusters, record sub-menus, a top navigation bar and full team (tenant) support. This is what makes Tungsten a good base for a SaaS app with many companies in one panel.
- Clusters: put several resources and pages behind one menu item, like "Settings". Make a class that extends
Clusterand setcluster = Settingson each resource. The cluster gets its own sub-menu, beside the page or as tabs on top. - Record sub-menu:
record_sub_navigation = ("view", "edit")gives each record its own tabs or side menu, with relation manager pages too. Overrideget_record_sub_navigationfor links that depend on the record. - Top navigation:
Panel(..., top_navigation=True)moves the menu from the sidebar into a bar at the top. - Tenant in the URL: with
Tenancy(Team, ..., url=True, slug_attribute="slug")every link holds the team, like/admin/acme/products. Two browser tabs can show two teams. - Team sign-up and settings: the
registrationfields add a "Register team" page (/admin/tenant/new). Theprofilefields add a team settings page, andcan_edit_profiledecides who may open it. - Roles per team:
RolesField().per_tenant()lets a user be an admin in one team and a viewer in another. - Billing hooks: subclass
BillingProviderfor Stripe, Paddle or Razorpay. The team menu gets a Billing link, andrequire_subscription=Truesends users of an unpaid team to billing first.
from tungsten.tenancy import Tenancy
panel = Panel(
...,
top_navigation=True,
tenancy=Tenancy(
Team, ownership="team_id", tenants=lambda user: user.teams,
url=True, slug_attribute="slug",
registration=[TextInput("name").required(), TextInput("slug").required()],
profile=[TextInput("name").required()],
),
)
Records of every resource whose model has the team_id column are filtered by the current team, and new records get it set on their own. Read the navigation docs and the multi-tenancy docs for every option.
Filament feature vs Tungsten now: how do they compare?
| Filament feature | Tungsten now |
|---|---|
| make:theme (custom Tailwind theme) | tungsten make:theme and theme:build |
| ModalTableSelect, Slider, ViewField | Yes, plus custom Field classes |
| Create / edit option in a Select | create_option_form(), edit_option_form() |
| Input masks | mask(), money_mask() (with Indian grouping) |
| Date picker timezone | timezone("Asia/Kolkata"), saved in UTC |
| Actions in forms, hints, prefixes and suffixes | Yes, plus Actions rows and callouts |
| Flex / Split, Text, Html, Callout | Yes |
| RichEditor toolbar, attachments, mentions, blocks | Yes, with render_rich_content() |
| Filters in a modal or dropdown, Apply button | layout="modal" / "dropdown", deferred_filters() |
| Remember filters, search and sort | persist_in_session() |
| Simple and cursor pagination | mode="simple" / "cursor" |
| Stacked and grid layouts | stacked_on_mobile(), content_grid() |
| Nested modals, chained actions, wizard modals | extra_modal_footer_actions(), then(), steps() |
| Download and streaming actions | DownloadAction, DownloadBulkAction |
| Chunked and background bulk actions | chunk_selected_records(), background() |
| Background exports, export history | ExportAction().background(), /admin/_tw/exports |
| CodeEntry, ViewEntry, entry actions | Yes |
| Pivot fields on attach, associate / dissociate | AttachAction().form(), AssociateAction |
| Polymorphic, has many through, nested resources | morph_many, dotted paths, related_resource |
| Email 2FA codes, verified email change | two_factor_email=True, on with email verification |
| Sanctum-style API tokens | api_tokens=True, panel.api_user() |
| Email and live (WebSocket) notifications | send_to_mail(), broadcast() |
| Clusters, record sub-navigation, top navigation | Cluster, record_sub_navigation, top_navigation=True |
| Tenant in URL, team sign-up and profile pages | Tenancy(url=True, registration=..., profile=...) |
| Per-team roles, tenant billing | RolesField().per_tenant(), BillingProvider |
What are the limits to know?
- A team's slug can't be the name of one of the panel's own routes, like
login,profileortenant. - Rate limit counts live in the server process memory, so they are per process.
- Background jobs run in a small thread pool next to the web server. They stop if the server stops; with one process, call
panel.jobs.fail_unfinished()at startup. - Nested modals go two levels deep at most.
- Pivot columns can't be sorted or searched, and "has many through" tables can't create or attach records.
- Custom themes must be rebuilt after each Tungsten upgrade.
Key facts
- Package:
tungsten-admin(Tungsten, a Filament-style admin panel for FastAPI). - Version: 0.1.9 on PyPI. Install with
pip install -U tungsten-admin. - Merged on 14 October 2026 in pull requests #55 to #62.
- Includes clusters, top navigation, tenant URLs, team sign-up, per-team roles and billing hooks.
- Python 3.10 or newer. License: MIT. Free and open source.
Get started
Visit the Tungsten website, browse all Tungsten plugins, or star the project on GitHub. Coming from Laravel? The Filament migration tool and its docs show how to move an existing Filament panel. And if you want an admin panel built around your business, talk to us.
Frequently asked questions
Is there a Filament alternative for Python and FastAPI?
Yes. Tungsten (tungsten-admin) is a free, open source, Filament-style admin panel for FastAPI. You build resources, forms, tables and actions from Python classes, and its latest update matches most Filament features.
Does Tungsten have Filament's make:theme?
Yes. Run tungsten make:theme and then tungsten theme:build to make your own Tailwind theme, and pass the built file to Panel(theme_stylesheet=...). Every Tailwind class then works in your templates.
How do I install the Tungsten Filament parity update?
Run pip install -U tungsten-admin to get version 0.1.9 from PyPI, then run panel.create_tables(engine) once to add the new tables.
Can Tungsten tables handle millions of rows?
Yes. Tungsten tables support paginated(mode="cursor"), which starts each page after the last row of the one before, so page 5,000 is as fast as page 1.
Does Tungsten have API tokens like Laravel Sanctum?
Yes. With Auth(api_tokens=True), users make personal API tokens with abilities and an expiry, and you protect FastAPI routes with Depends(panel.api_user("orders:read")). Only a SHA-256 hash of each token is stored.
Can Tungsten run big exports in the background?
Yes. ExportAction().background() builds the file in the background and sends a "Your export is ready" notification with a Download button. Every export is also kept in the user's export history.
What does the Tungsten activity log record?
With Panel(activity_log=True), Tungsten logs creates, edits, deletes, restores, force deletes, every action run, exports, sign-ins, sign-outs, failed sign-ins and password resets. You can log your own events with panel.log_activity.
Does Tungsten support Filament-style navigation clusters and tenant URLs?
Yes. A Cluster class groups resources behind one menu item with its own sub-menu, Panel(top_navigation=True) puts the menu in a top bar, and Tenancy(..., url=True) puts the team in every link, like /admin/acme/products. Teams also get sign-up and settings pages, per-team roles and billing hooks.
Need Expert IT Solutions?
Ready to transform your business? Team up with Prism Infoways and harness cutting-edge technology for your growth.
Contact Us




