Build a tagging system in FlutterFlow using two Firestore collections: tags (with name, slug, and count fields) and your content collection (with a tags Array field). Display tags as Chips inside a Wrap widget. Use arrayContains in Firestore queries to filter content by tag. Keep a count field on each tag document and update it with FieldValue.increment when content is tagged or untagged.
Firestore Tagging Architecture for FlutterFlow Apps
Tags are one of the most common content features — and one of the most commonly over-engineered. The right Firestore architecture stores tags as an Array field directly on each content document, which unlocks Firestore's native arrayContains query operator. A separate tags collection tracks all unique tag names with their usage counts for autocomplete and tag cloud displays. This tutorial covers the complete implementation: Firestore schema, the FlutterFlow Wrap+Chip tag cloud, filtering logic, and a simple admin page for managing your tag library.
Prerequisites
- FlutterFlow project with Firestore connected
- An existing content collection in Firestore (articles, posts, or products)
- Firebase Authentication set up for admin actions
- Basic knowledge of FlutterFlow's Query Collection and Filtering features
Step-by-step guide
Design the Firestore schema for tags and content
Design the Firestore schema for tags and content
In FlutterFlow's Firestore panel (left sidebar → Firestore), open your content collection — let's call it articles. Add a new field named tags with type String Array. This single field stores all tag slugs for that article, like ['flutter', 'firebase', 'tutorial']. Next, create a new top-level collection named tags. Each document uses the tag slug as the document ID (e.g., 'flutter-tips') and has three fields: name (String — display name like 'Flutter Tips'), slug (String — URL-safe identifier like 'flutter-tips'), and count (Integer — number of articles using this tag). This two-collection approach keeps queries fast and storage minimal.
Expected result: Your articles collection has a tags Array field and a new tags collection exists with name, slug, and count fields visible in the FlutterFlow Firestore schema editor.
Build the tag cloud display with Wrap and Chip widgets
Build the tag cloud display with Wrap and Chip widgets
On your article list or detail page, add a Container widget to hold the tag cloud. Inside it, add a Generate Dynamic Children widget (or in newer FlutterFlow versions, a Builder widget with a list) pointed at the tags Array field from your article document. For each tag string in the array, generate a Chip widget. Set the Chip's label to the current tag value, background color to your brand color with low opacity, and text color to your brand primary. Wrap the entire list of chips in a Wrap widget with runSpacing: 8 and spacing: 8 so chips flow naturally to the next line when the row fills up.
Expected result: Each article displays its tags as a horizontal row of colored chips that wraps to multiple lines when there are more than 3-4 tags.
Implement arrayContains filtering for the article list
Implement arrayContains filtering for the article list
On your article list page, add a Page State variable named activeTag (String, default empty). Your article list query should use a Conditional Filter: when activeTag is not empty, add a Firestore filter on the tags field using the 'Array Contains' operator with activeTag as the value. When activeTag is empty, show all articles without the filter. In FlutterFlow's Query Collection dialog, click 'Filter', select the tags field, choose 'Array Contains', and bind the value to activeTag. Add a 'Clear Filter' button that resets activeTag to empty to show all content again.
Expected result: Tapping a tag chip updates activeTag and the article list instantly re-queries to show only articles with that tag. Tapping 'Clear Filter' restores the full list.
Build the tag picker component for content creation
Build the tag picker component for content creation
When creating or editing an article, users need to select existing tags and optionally create new ones. Create a Reusable Component named TagPickerComponent. Inside it, add a Query Collection on the tags collection ordered by count descending to show most-used tags first. Display each tag as a selectable Chip — when tapped, add its slug to a Component State variable named selectedTags (String Array). Show selected tags in a second Wrap widget at the top with an X button to remove them. Add a text field at the bottom for typing new tag names, and a Custom Action that creates the new tag document in Firestore when the user taps Add.
Expected result: The TagPickerComponent shows all existing tags, lets users select multiple, and allows creating new tags — all within a single reusable component.
Update tag counts when articles are created, edited, or deleted
Update tag counts when articles are created, edited, or deleted
Tag counts keep the tag cloud sorted by popularity. When a user saves an article with tags, create a Custom Action named updateTagCounts. This action takes two lists: addedTags and removedTags (String Arrays). For each tag in addedTags, update the corresponding tags document using FieldValue.increment(1). For each tag in removedTags, use FieldValue.increment(-1). If creating a new tag for the first time, use set() with merge: true to create the document with count: 1. Call this action from both the Create Article and Edit Article flows, passing the difference between the old and new tag arrays.
1// Custom Action: updateTagCounts2import 'package:cloud_firestore/cloud_firestore.dart';34Future<void> updateTagCounts({5 required List<String> addedSlugs,6 required List<String> removedSlugs,7}) async {8 final batch = FirebaseFirestore.instance.batch();910 for (final slug in addedSlugs) {11 final ref = FirebaseFirestore.instance.collection('tags').doc(slug);12 batch.set(13 ref,14 {'count': FieldValue.increment(1)},15 SetOptions(merge: true),16 );17 }1819 for (final slug in removedSlugs) {20 final ref = FirebaseFirestore.instance.collection('tags').doc(slug);21 batch.update(ref, {'count': FieldValue.increment(-1)});22 }2324 await batch.commit();25}Expected result: After saving an article, the count field on each selected tag's Firestore document increases by 1, and the tag cloud re-sorts to show the most popular tags first.
Complete working example
1// Custom Action: saveArticleWithTags2// Called from the article creation/edit form3import 'package:cloud_firestore/cloud_firestore.dart';4import 'package:firebase_auth/firebase_auth.dart';56Future<String> saveArticleWithTags({7 required String title,8 required String body,9 required List<String> selectedTagSlugs,10 String? existingArticleId,11}) async {12 final userId = FirebaseAuth.instance.currentUser?.uid;13 if (userId == null) throw Exception('User not authenticated');1415 final db = FirebaseFirestore.instance;16 final batch = db.batch();1718 // Determine added vs removed tags for count updates19 List<String> previousTags = [];20 if (existingArticleId != null) {21 final existing = await db.collection('articles').doc(existingArticleId).get();22 previousTags = List<String>.from(existing.data()?['tags'] ?? []);23 }24 final addedTags = selectedTagSlugs.where((t) => !previousTags.contains(t)).toList();25 final removedTags = previousTags.where((t) => !selectedTagSlugs.contains(t)).toList();2627 // Write or update the article28 final articleRef = existingArticleId != null29 ? db.collection('articles').doc(existingArticleId)30 : db.collection('articles').doc();3132 batch.set(33 articleRef,34 {35 'title': title,36 'body': body,37 'tags': selectedTagSlugs,38 'author_id': userId,39 'updated_at': FieldValue.serverTimestamp(),40 if (existingArticleId == null) 'created_at': FieldValue.serverTimestamp(),41 },42 SetOptions(merge: true),43 );4445 // Update tag counts atomically in the same batch46 for (final slug in addedTags) {47 batch.set(48 db.collection('tags').doc(slug),49 {'count': FieldValue.increment(1)},50 SetOptions(merge: true),51 );52 }53 for (final slug in removedTags) {54 batch.update(55 db.collection('tags').doc(slug),56 {'count': FieldValue.increment(-1)},57 );58 }5960 await batch.commit();61 return articleRef.id;62}Common mistakes when building a Content Tagging System in FlutterFlow
Why it's a problem: Storing tags as a subcollection instead of an Array field on the document
How to avoid: Store tags as a String Array field directly on your content document. Use the separate tags collection only for the tag library (names, slugs, counts) — not for the per-document tag assignments.
Why it's a problem: Using tag display names instead of slugs in the Array field
How to avoid: Always normalize tags to lowercase slugs (e.g., 'flutter-tips') before storing. Display the human-readable name from the tags collection, but query using slugs.
Why it's a problem: Querying the tags collection on every keystroke in a search field for autocomplete
How to avoid: Load the full tags collection once on page load into a Page State variable (it is rarely more than a few hundred documents), then filter the in-memory list as the user types.
Best practices
- Store tags as a String Array on content documents and use a separate tags collection only for the tag library with names and counts.
- Normalize all tag identifiers to lowercase slugs before storing to prevent case-sensitivity duplicates.
- Load the complete tags list once into Page State on the filter page and perform in-memory filtering for autocomplete to minimize Firestore reads.
- Set Firestore security rules to allow tag document creation only by authenticated users and tag deletion only by admin roles.
- Add a Firestore composite index on (tags, created_at) if you need to filter by tag and sort by date simultaneously.
- Show tag counts next to each tag in the picker so users know which tags are most used before selecting.
- Create a Cloud Function that merges duplicate tags when needed — for example, combining 'firebase' and 'Firebase' into a single canonical tag.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a FlutterFlow app with a Firestore backend. Explain the best architecture for a tagging system where content documents can have multiple tags, users can filter content by tag, and I can display a sortable tag cloud. Include the Firestore schema and query approach.
Add a tagging system to my FlutterFlow app. Create a tags Firestore collection with name, slug, and count fields. Add a tags String Array field to my articles collection. Build a tag cloud page that uses arrayContains to filter articles when a tag chip is tapped.
Frequently asked questions
Should I store tags as an Array field or a subcollection in Firestore?
Always use an Array field on the content document for tags. Subcollections require extra queries per document and cannot use Firestore's arrayContains filter. The Array approach is faster and cheaper.
How does the Firestore arrayContains filter work?
arrayContains checks whether a specific value exists anywhere in an Array field. In FlutterFlow's Query Collection dialog, select your tags field, choose 'Array Contains', and bind the value to your activeTag state variable. The query returns only documents where the tags array includes that exact value.
Can I filter by multiple tags at the same time?
Firestore supports arrayContainsAny for OR filtering (documents that have any of the specified tags). For AND filtering (documents that have all specified tags), Firestore does not support it natively — you need to filter by the most selective tag first and then filter the results client-side.
How many tags can a Firestore Array field hold?
Firestore documents have a 1 MB size limit. Each string tag is typically 10-30 bytes, so you can store thousands of tags in a single Array field before approaching the limit. In practice, content items rarely have more than 20 tags.
Do I need FlutterFlow Pro for a tagging system?
The basic tagging system — Array fields, Chip display, arrayContains filtering — can be built entirely within FlutterFlow's visual editor on the Free plan. The Custom Action for batch tag count updates requires Pro only if you write custom Dart code.
How do I prevent duplicate tags being created?
Use the tag slug as the Firestore document ID. When creating a new tag, use set() with merge: true. If a document with that slug already exists, the merge prevents creating a duplicate and only increments the count.
How do I display a tag cloud with tags sized by popularity?
Query the tags collection ordered by count descending. In your Wrap widget, set each Chip's font size proportionally to the tag's count — for example, 12 + (count / maxCount * 8) gives a range of 12px to 20px. Normalize against the highest-count tag in your current dataset.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation