Tungsten Filament Parity Update: a Filament Alternative for Your Python FastAPI Admin Panel

|
Category:Software DevelopmentTags:Filament alternativePython admin panelFastAPI admin panelTungstentungsten-adminLaravel FilamentFilament for Pythonadmin panel themesAPI tokensactivity logbackground exportsopen source admin
Tungsten Filament Parity Update: a Filament Alternative for Your Python FastAPI Admin Panel

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.

13 min read

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.

  1. Install or upgrade Tungsten:
    pip install -U tungsten-admin
  2. Some features add new tables (jobs, API tokens, email changes). Create them once after upgrading:
    panel.create_tables(engine)
  3. 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:theme writes theme.css, tailwind.config.js, package.json and .gitignore into admin/theme/.
  • tungsten theme:build builds it, and --watch rebuilds 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) and ViewField for your own template. The docs now show how to write a custom field by subclassing Field.
  • 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, and money_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 Actions row or in a callout. They can fill other fields.
  • Layouts and rich text: Flex (alias Split), Text, Html, Callout, tabs kept in the URL, and a RichEditor with toolbar choice, attachments, @mentions and 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" or layout="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, and Range() 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() and after() hooks, and rate_limit(3, per_seconds=60). Counts live in the server process memory.
  • Downloads: DownloadAction sends 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, and background() 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_rows sets 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: CodeEntry shows code or JSON with colours and a copy button. ViewEntry renders 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 AssociateAction and DissociateAction.
  • Polymorphic and "has many through": morph_many, morph_to_many, and dotted paths like "projects.tasks".
  • Own pages and nested resources: separate_page = True, or related_resource for 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. SecurityHeadersMiddleware can 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 Cluster and set cluster = Settings on 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. Override get_record_sub_navigation for 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 registration fields add a "Register team" page (/admin/tenant/new). The profile fields add a team settings page, and can_edit_profile decides 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 BillingProvider for Stripe, Paddle or Razorpay. The team menu gets a Billing link, and require_subscription=True sends 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 featureTungsten now
make:theme (custom Tailwind theme)tungsten make:theme and theme:build
ModalTableSelect, Slider, ViewFieldYes, plus custom Field classes
Create / edit option in a Selectcreate_option_form(), edit_option_form()
Input masksmask(), money_mask() (with Indian grouping)
Date picker timezonetimezone("Asia/Kolkata"), saved in UTC
Actions in forms, hints, prefixes and suffixesYes, plus Actions rows and callouts
Flex / Split, Text, Html, CalloutYes
RichEditor toolbar, attachments, mentions, blocksYes, with render_rich_content()
Filters in a modal or dropdown, Apply buttonlayout="modal" / "dropdown", deferred_filters()
Remember filters, search and sortpersist_in_session()
Simple and cursor paginationmode="simple" / "cursor"
Stacked and grid layoutsstacked_on_mobile(), content_grid()
Nested modals, chained actions, wizard modalsextra_modal_footer_actions(), then(), steps()
Download and streaming actionsDownloadAction, DownloadBulkAction
Chunked and background bulk actionschunk_selected_records(), background()
Background exports, export historyExportAction().background(), /admin/_tw/exports
CodeEntry, ViewEntry, entry actionsYes
Pivot fields on attach, associate / dissociateAttachAction().form(), AssociateAction
Polymorphic, has many through, nested resourcesmorph_many, dotted paths, related_resource
Email 2FA codes, verified email changetwo_factor_email=True, on with email verification
Sanctum-style API tokensapi_tokens=True, panel.api_user()
Email and live (WebSocket) notificationssend_to_mail(), broadcast()
Clusters, record sub-navigation, top navigationCluster, record_sub_navigation, top_navigation=True
Tenant in URL, team sign-up and profile pagesTenancy(url=True, registration=..., profile=...)
Per-team roles, tenant billingRolesField().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, profile or tenant.
  • 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

Related Blogs