Release and build versions
The storefront release is Astro Chat 1.0. The plugin package included with that release identifies its editor, schema and generated runtime as 1.6.1.
These numbers describe different layers:
| Version | Meaning |
|---|---|
Product release 1.0 |
The public release shown on the GoldAstro product page and in release notes |
Plugin/runtime 1.6.1 |
The internal build written into the supplied Studio plugin and generated runtime |
Schema 7 |
The configuration structure used by the included build |
When asking for support, include both the product release and the internal plugin/runtime build.
Before you install
Astro Chat is a Roblox Studio plugin built for the modern TextChatService system. It is not a replacement chat service and does not provide a separate hosted backend.
You need:
- Roblox Studio and permission to install plugins.
- An experience using
TextChatServicerather than the legacy chat system. - A safe test place or unpublished copy of the experience.
- Test accounts for every important UserId or group-rank rule.
Back up or publish a Studio version before the first Apply. If another script already assigns TextChatService.OnIncomingMessage, OnChatWindowAdded or OnBubbleAdded, plan the integration carefully because only one callback can occupy each property at a time.
Apply, Save, Repair and Remove Runtime must be performed in Edit mode. The plugin intentionally refuses to rewrite generated scripts during Play or Play Here.
Quick start
- Install Astro Chat from the Roblox Creator Store when the listing becomes available.
- Open the experience in Roblox Studio.
- Select Astro Chat from the Plugins toolbar.
- Open Chat and choose the global chat and bubble appearance.
- Open Tags and configure the supplied Owner, Developer, Moderator and VIP examples or create your own.
- Set each tag’s access rule before enabling Auto equip or exposing a client-side tag picker.
- Open Messages and configure the system message types the experience will send.
- Stop Play or Play Here, return to Edit mode and select Apply.
- Start Play and test real player messages, bubble chat and server messages.
- Return to Edit mode and select Save after any final changes.
The @everyone tag and message type are protected fallbacks. They cannot be deleted, and the fallback tag keeps priority 1.
Generated runtime
Apply creates or updates this managed structure:
ReplicatedStorage
└── AstroChat
├── AstroChatAPI
├── Bridge
├── Tags
└── MessageTypes
ServerScriptService
└── AstroChat
└── AstroChatServer
StarterPlayer
└── StarterPlayerScripts
└── AstroChat
└── AstroChatClient
ReplicatedStorage.AstroChat also stores global configuration as attributes. Tag and message-type definitions are folders whose settings are attributes.
Do not edit the generated AstroChatAPI, AstroChatServer or AstroChatClient source directly. Use the plugin and select Save or Settings → Repair generated runtime after a plugin update. Manual edits can be replaced the next time Astro Chat writes the runtime.
Runtime responsibilities
AstroChatAPIexposes the supported server and client functions.Bridgecarries limited tag requests and styled system messages between server and clients.AstroChatServerapplies automatic tags and validates client tag requests.AstroChatClientformats incoming player messages, system messages and speech bubbles locally through TextChatService.
The configuration and runtime are stored in the place. The plugin itself does not need to remain open when a published server runs.
Chat configuration
The Chat panel edits Roblox TextChatService presentation. The included build supports:
- Chat font and weight.
- Message text size and color.
- Chat-window background color and transparency.
- Input-bar background, text, placeholder color and text size.
- Chat-window width and height scale.
- Chat-window, input-bar and channel-tab visibility.
- Bubble-chat visibility, font, text size, text color, background color and transparency.
- Tag visibility, tag separator and front/back placement.
- Single-tag or stacked-tag display.
- Priority-based player-name and message colors.
- Name suffix text and white, strongest-tag or custom suffix color.
- Live synchronization during play testing.
Use the property search field to jump to a setting in a long panel.
Stacked-tag behavior
When Stack equipped tags is enabled, every explicitly equipped tag prefix can be shown. Tags are sorted by priority, then by name when priorities match. The highest-priority equipped tag controls the username color, optional message color, per-message font size and tag-specific bubble override.
When stacking is disabled, equipping a tag replaces the previously equipped non-fallback tag.
Name and message styling
Color player names uses the strongest tag’s username color. A blank per-tag message color inherits the global chat text color. A tag font size of -1 inherits the global chat size.
Player tags
The supplied examples are Owner, Developer, Moderator and VIP, plus the protected Everyone fallback. Examples are starting points only; configure their access before publishing.
Each non-fallback tag supports:
| Setting | Purpose |
|---|---|
| Tag ID | Stable name used by the API |
| Display name | Human-readable name in the editor |
| Prefix | Text shown around the player’s name |
| Placement | Shows the prefix in front of or behind the name |
| Tag, username and message colors | Separates prefix, name and message presentation |
| Font size and emphasis | Overrides prefix size, bold, italic or underline styling |
| Priority | Determines ordering and the strongest style when tags stack |
| Enabled | Makes the tag available to the runtime |
| Allow everyone | Allows any player to equip the tag |
| Auto equip | Equips the tag automatically for eligible players |
| Allowed UserIds | Comma-separated account IDs allowed to use the tag |
| Group ID and minimum rank | Allows members at or above a group rank |
Use -1 for an unused UserId, group ID or minimum group rank field. Keep tag IDs at 40 characters or fewer and use letters, numbers, underscores or hyphens.
Access rules
A player is eligible when the tag is enabled and at least one configured rule allows access. The protected fallback is always available. Non-fallback tags may use an open-to-everyone rule, an explicit UserId list, or a valid group and minimum rank.
The server checks access for both direct server calls and client requests. Hiding a tag button in local UI is not a permission boundary.
Tag-specific speech bubbles
Enable Bubble custom style to let the strongest tag override bubble text color, background color, transparency, text size, font, weight and tail visibility. Blank colors and numeric values of -1 inherit global bubble settings.
Auto equip
Auto-equip runs when a player joins. If multiple eligible tags have Auto equip enabled, the highest-priority eligible tag wins. With tag stacking enabled, players can later add other permitted tags through the API.
Message types
Message types style server-created system messages. The supplied build includes System, Announcement, Warning and Error, plus the protected Everyone fallback.
A message type controls:
- Stable type ID and editor display name.
- Prefix and color.
- Optional font-size override.
- Bold, italic and underline prefix styling.
- Whether a non-fallback type is enabled.
Use the preview Send button during Play or Play Here to test a type through Roblox’s real chat. A font size of -1 inherits the global chat size.
Message types do not bypass Roblox text filtering for player-authored content. Do not relay untrusted player text through server system messages as a way to avoid normal filtering.
Runtime API
The same AstroChatAPI ModuleScript can be required by server Scripts and LocalScripts. Individual methods enforce where they may run. Use the server for authoritative game decisions and LocalScripts only for a player’s permitted tag requests.
Require the API
From a server Script:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local AstroChat = ReplicatedStorage:WaitForChild("AstroChat")
local ChatAPI = require(AstroChat:WaitForChild("AstroChatAPI"))
From a LocalScript, use the same path:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ChatAPI = require(
ReplicatedStorage:WaitForChild("AstroChat"):WaitForChild("AstroChatAPI")
)
The API exposes ChatAPI.Version, which is 1.6.1 in the package supplied with product release 1.0.
API quick reference
| Method | Context | Result |
|---|---|---|
GetTag(tagId) |
Server or client | One enabled tag Folder, or nil |
GetMessageType(typeId) |
Server or client | One enabled message-type Folder, or nil |
GetTags() |
Server or client | All tag Folders, sorted by priority |
GetMessageTypes() |
Server or client | All message-type Folders, sorted by ID |
IsTagAllowed(player, tagId) |
Server only | Boolean permission result |
GetEquippedTags(player) |
Server or client | Equipped tag Folders in priority order |
GetEquippedTag(player) |
Server or client | Strongest equipped tag Folder, or nil |
EquipTag(player, tagId) |
Server only | Success boolean and tag ID or error message |
UnequipTag(player, tagId) |
Server only | Success boolean and optional error message |
ClearTags(player) |
Server only | Success boolean and optional error message |
RequestTag(tagId) |
Client only | Whether a valid request was sent |
RequestUnequipTag(tagId) |
Client only | Whether a valid request was sent |
RequestClearTags() |
Client only | Whether a valid request was sent |
SendMessage(text, typeId, target?) |
Server only | Success boolean and optional error message |
GetTag
local tag = ChatAPI:GetTag("Moderator")
if tag then
print(tag:GetAttribute("DisplayName"))
print(tag:GetAttribute("Priority"))
end
GetTag(tagId) returns the enabled managed tag Folder with that exact ID. It returns nil for an unknown or disabled tag. Use the Folder’s attributes to read its configured presentation; do not change those attributes at runtime as a substitute for saving through the plugin.
GetMessageType
local messageType = ChatAPI:GetMessageType("Announcement")
if messageType then
print(messageType:GetAttribute("Prefix"))
print(messageType:GetAttribute("Color"))
end
GetMessageType(typeId) returns the enabled message-type Folder or nil. Check it before sending a message when the type name comes from configuration.
GetTags
local tags = ChatAPI:GetTags()
for _, tag in ipairs(tags) do
print(tag.Name, tag:GetAttribute("Priority"))
end
GetTags() returns all managed tag Folders in display priority order. It is useful for building an in-game tag selector, but the server still decides which tags a player may equip.
GetMessageTypes
for _, messageType in ipairs(ChatAPI:GetMessageTypes()) do
print(messageType.Name, messageType:GetAttribute("DisplayName"))
end
GetMessageTypes() returns message-type Folders alphabetically by ID. This is useful for developer tools or validating a configurable message type.
IsTagAllowed
local allowed = ChatAPI:IsTagAllowed(player, "VIP")
if allowed then
print(player.Name, "may use VIP")
end
IsTagAllowed(player, tagId) is server-only. It checks that the Player is valid, the tag exists and is enabled, and one of its configured access rules allows the player. On a client it always returns false.
GetEquippedTags
local equipped = ChatAPI:GetEquippedTags(player)
for index, tag in ipairs(equipped) do
print(index, tag.Name)
end
GetEquippedTags(player) returns the player’s explicit non-fallback tags in priority order. An empty table means the player is using only the fallback presentation.
GetEquippedTag
local strongest = ChatAPI:GetEquippedTag(player)
if strongest then
print("Strongest tag:", strongest.Name)
else
print("Using the fallback tag")
end
GetEquippedTag(player) is a convenience method that returns the first, strongest explicit tag or nil.
EquipTag
EquipTag(player, tagId) is server-only and always checks the configured tag permission. When stacking is enabled, it adds the new tag without duplicating it. When stacking is disabled, it replaces the current explicit tag.
local ok, tagIdOrError = ChatAPI:EquipTag(player, "VIP")
if ok then
print("Equipped:", tagIdOrError)
else
warn("Could not equip tag:", tagIdOrError)
end
Equipping the protected fallback clears explicit tags and returns the fallback tag ID.
UnequipTag
local ok, errorMessage = ChatAPI:UnequipTag(player, "VIP")
if not ok then
warn(errorMessage)
end
UnequipTag(player, tagId) is server-only. It removes the matching explicit tag and preserves any other equipped tags.
ClearTags
local ok, errorMessage = ChatAPI:ClearTags(player)
if not ok then
warn(errorMessage)
end
ClearTags(player) is server-only and removes every explicit tag so the fallback presentation is used. ClearTag is a compatibility alias that performs the same clear-all operation; despite the singular name, it does not accept a tag ID.
RequestTag
Use this from a LocalScript when the player selects a tag in your own UI:
local sent, errorMessage = ChatAPI:RequestTag("VIP")
if not sent then
warn(errorMessage)
end
RequestTag(tagId) is client-only. A true result means the request was well formed and sent; it does not mean the tag was granted. The generated server validates permission before equipping it.
RequestUnequipTag
local sent, errorMessage = ChatAPI:RequestUnequipTag("VIP")
if not sent then
warn(errorMessage)
end
RequestUnequipTag(tagId) is client-only and asks the server to remove one explicit tag.
RequestClearTags
local sent, errorMessage = ChatAPI:RequestClearTags()
if not sent then
warn(errorMessage)
end
RequestClearTags() is client-only and asks the server to return the player to fallback presentation.
SendMessage
SendMessage(text, messageType, target) is server-only. Omit target to broadcast, pass one Player for a private message, or pass an array of Players for a selected audience.
-- Everyone
ChatAPI:SendMessage("The round begins in 30 seconds.", "Announcement")
-- One player
ChatAPI:SendMessage("Your loadout was saved.", "System", player)
-- Selected players
ChatAPI:SendMessage(
"Moderator briefing ready.",
"Warning",
{leadModerator, respondingModerator}
)
Always check the return value when delivery is important:
local ok, errorMessage = ChatAPI:SendMessage(
"Your daily reward is ready.",
"System",
player
)
if not ok then
warn("Astro Chat message failed:", errorMessage)
end
The message type must exist and be enabled. The function returns false with an explanation for an empty message, unknown type or invalid target. If messageType is not a string, it defaults to System.
Keep server system messages short and trustworthy. Do not pass raw user-submitted text into SendMessage without applying the filtering and policy required for that text.
Use-case recipes
These recipes assume Astro Chat has already been applied and the referenced tag or message-type IDs exist in the plugin configuration.
Automatically equip a configured tag when players join
For a UserId or group-rank tag, the simplest method is built in:
- Open Tags in the plugin.
- Select the tag.
- Configure Allowed UserIds, or Group id and Minimum group rank.
- Turn Auto equip on.
- Save the runtime in Edit mode.
The generated server checks new players and equips the highest-priority eligible automatic tag. No extra Script is required.
For game-specific logic, place this Script in ServerScriptService:
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ChatAPI = require(
ReplicatedStorage:WaitForChild("AstroChat"):WaitForChild("AstroChatAPI")
)
Players.PlayerAdded:Connect(function(player)
if ChatAPI:IsTagAllowed(player, "Member") then
local ok, errorMessage = ChatAPI:EquipTag(player, "Member")
if not ok then
warn("Member tag failed for", player.Name, errorMessage)
end
end
end)
The Member tag still needs an Astro Chat access rule that permits the player. Server code cannot use EquipTag to bypass the plugin’s permission configuration.
Send a welcome message to the joining player
Place this Script in ServerScriptService:
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ChatAPI = require(
ReplicatedStorage:WaitForChild("AstroChat"):WaitForChild("AstroChatAPI")
)
Players.PlayerAdded:Connect(function(player)
task.delay(2, function()
if not player.Parent then
return
end
ChatAPI:SendMessage(
"Welcome! Use /help to view the available commands.",
"System",
player
)
end)
end)
The short delay lets the player’s chat client finish loading before the targeted message is sent.
Announce a staff member joining
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ChatAPI = require(
ReplicatedStorage:WaitForChild("AstroChat"):WaitForChild("AstroChatAPI")
)
Players.PlayerAdded:Connect(function(player)
if ChatAPI:IsTagAllowed(player, "Moderator") then
ChatAPI:EquipTag(player, "Moderator")
ChatAPI:SendMessage(
"A moderator has joined the server.",
"Announcement"
)
end
end)
Avoid broadcasting private staff status if your moderation policy requires staff to remain undisclosed.
Send timed round announcements
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ChatAPI = require(
ReplicatedStorage:WaitForChild("AstroChat"):WaitForChild("AstroChatAPI")
)
local function announceCountdown(seconds)
for remaining = seconds, 1, -1 do
ChatAPI:SendMessage(
string.format("Round starts in %d...", remaining),
remaining <= 3 and "Warning" or "Announcement"
)
task.wait(1)
end
ChatAPI:SendMessage("The round has started!", "Announcement")
end
announceCountdown(5)
For a real round system, call announceCountdown() from the authoritative server round controller instead of creating an endless standalone loop.
Notify only one player about a saved action
local function confirmSave(player)
local ok, errorMessage = ChatAPI:SendMessage(
"Your settings were saved successfully.",
"System",
player
)
if not ok then
warn(errorMessage)
end
end
Targeted system messages are useful for purchases, inventory actions, quests and validation errors that should not fill the public chat.
Message a selected team or staff list
local Players = game:GetService("Players")
local function getPlayersOnTeam(teamName)
local selected = {}
for _, player in ipairs(Players:GetPlayers()) do
if player.Team and player.Team.Name == teamName then
table.insert(selected, player)
end
end
return selected
end
local defenders = getPlayersOnTeam("Defenders")
if #defenders > 0 then
ChatAPI:SendMessage(
"Return to the base and prepare for the next wave.",
"Warning",
defenders
)
end
The target array should contain Player instances. Empty arrays are accepted but naturally reach nobody.
Equip group staff automatically on join
Configure a staff tag with the correct Group id, Minimum group rank and Auto equip. The built-in auto-equip path is enough for normal use. If another server system needs to run alongside it, you can check the same rule explicitly:
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
if not ChatAPI:IsTagAllowed(player, "Moderator") then
return
end
local ok, errorMessage = ChatAPI:EquipTag(player, "Moderator")
if not ok then
warn(errorMessage)
return
end
ChatAPI:SendMessage(
"Moderator tools are ready.",
"System",
player
)
end)
The group lookup is protected with an internal service-call check. If Roblox cannot return a rank, permission is not granted by that rule.
Equip an explicit owner or developer account
Add the account’s numeric ID to the tag’s Allowed UserIds field, then enable Auto equip. For a deliberate manual workflow:
local OWNER_USER_ID = 123456789
local function equipOwnerTag(player)
if player.UserId ~= OWNER_USER_ID then
return
end
if not ChatAPI:IsTagAllowed(player, "Owner") then
warn("Owner UserId is missing from the Astro Chat tag configuration")
return
end
ChatAPI:EquipTag(player, "Owner")
end
Replace the example ID and keep the matching Allowed UserIds configuration in Astro Chat. The explicit check documents your game’s intent; Astro Chat’s server permission remains the final gate.
Let players choose from their allowed tags
Build your own tag menu in a LocalScript. Populate it from GetTags(), then send only the selected ID through RequestTag():
local availableTags = ChatAPI:GetTags()
for _, tag in ipairs(availableTags) do
print("Tag option:", tag.Name)
end
TagButton.Activated:Connect(function()
local sent, errorMessage = ChatAPI:RequestTag("VIP")
if not sent then
warn(errorMessage)
end
end)
ClearTagButton.Activated:Connect(function()
ChatAPI:RequestClearTags()
end)
The client can display possibilities, but it cannot authoritatively determine eligibility. The server rejects a tag whose access rule does not permit the requesting player.
Restore a saved tag preference
Save only the selected tag ID in your own data system. After loading it on the server, validate it through Astro Chat before equipping:
local function restoreSavedTag(player, savedTagId)
if type(savedTagId) ~= "string" then
return
end
if not ChatAPI:GetTag(savedTagId) then
return
end
if ChatAPI:IsTagAllowed(player, savedTagId) then
local ok, errorMessage = ChatAPI:EquipTag(player, savedTagId)
if not ok then
warn(errorMessage)
end
end
end
Do not save Folder instances. Store the stable tag ID and re-check permission every time the player joins because roles and entitlements can change.
Clear temporary tags between rounds
local Players = game:GetService("Players")
local function clearRoundTags()
for _, player in ipairs(Players:GetPlayers()) do
ChatAPI:UnequipTag(player, "RoundWinner")
ChatAPI:UnequipTag(player, "MVP")
end
end
Use UnequipTag when permanent identity tags should remain. Use ClearTags only when every explicit tag should be removed.
Validate message types before using configurable IDs
local function sendConfiguredMessage(text, configuredType)
local messageType = ChatAPI:GetMessageType(configuredType)
if not messageType then
warn("Unknown Astro Chat message type:", configuredType)
return false
end
return ChatAPI:SendMessage(text, messageType.Name)
end
This pattern is useful when another server module chooses a message type by configuration rather than a hard-coded constant.
Studio preview and saving
Preview chat accurately
Run Play or Play Here, open Astro Chat and use Roblox’s actual chat window. The selection previews in the editor help compare styles, but the real TextChatService window is the final rendering environment.
The Equip and Unequip controls in the Tags preview are Studio-only visual overrides. They do not grant published players access and do not replace server permission checks.
Apply, Save and Load
- Apply installs the generated runtime for the first time.
- Save writes configuration changes into an existing managed runtime.
- Load imports the installed runtime configuration into the plugin editor.
The plugin tracks changes and labels an installed runtime as up to date or unsaved. While the editor remains open, it also periodically saves changed configuration. Always perform an explicit Save before closing Studio or publishing.
With Live sync enabled, supported presentation changes are applied to the temporary play-test DataModel. Play-test changes are restored to Edit mode when the test stops, then must be saved to become part of the place.
Upgrading and repairing
Use this upgrade sequence:
- Back up the place and publish a recoverable Studio version.
- Stop every running play test.
- Update the Astro Chat plugin.
- Open the plugin and select Load if an existing runtime is present.
- Review new or migrated settings.
- Open Settings and select Repair generated runtime.
- Save, run a complete test and publish only after the test passes.
The included build can migrate the earlier ChatPro root and generated object names when it recognizes managed configuration. Version 1.6.1 also removes the earlier experimental global/cross-server message path and clears its obsolete attributes.
Removing the runtime
Remove runtime, keep configuration removes the managed API, bridge, server and client scripts while retaining the ReplicatedStorage.AstroChat configuration. Use it when temporarily disabling the installed runtime or preparing a clean regeneration.
Do not manually delete only one generated script. A partial runtime can leave configuration present while chat formatting no longer runs consistently.
Security
Use these rules in every production experience:
- Grant tags through explicit UserIds or the smallest suitable group rank.
- Keep sensitive or staff-only tags disabled until their rules are verified with test accounts.
- Treat every LocalScript request as untrusted input.
- Use
EquipTagon the server for game-owned entitlement decisions. - Never use client visibility or a local tag picker as proof of permission.
- Do not expose private configuration or credentials in tag text or system messages.
- Filter untrusted text according to Roblox requirements before displaying it.
- Review every script that assigns TextChatService formatting callbacks.
- Test with an unauthorized account, not only an owner account.
Astro Chat’s runtime validates client tag requests on the server. It does not determine whether a purchase, game pass, subscription or external entitlement is legitimate; your server code must verify those systems before calling EquipTag.
Troubleshooting
The plugin opens, but the game has no Astro Chat runtime
Stop Play or Play Here, return to Edit mode and select Apply. Confirm the generated structure exists in ReplicatedStorage, ServerScriptService and StarterPlayerScripts.
Apply or Save is blocked
Astro Chat does not write generated source during a play test. Stop the test completely and try again in Edit mode.
Chat styles do not appear
Confirm the experience uses TextChatService. Look for another LocalScript assigning TextChatService.OnIncomingMessage, OnChatWindowAdded or OnBubbleAdded; a later assignment can replace Astro Chat’s formatter.
A tag does not auto-equip
Check that the tag is enabled, Auto equip is on, and at least one access rule permits the test player. For group rules, verify the numeric group ID and that minimum rank is between 0 and 255. Test in a server session with a real account.
A client tag request returns true but nothing changes
The client return value only confirms that the request was sent. Check the tag ID, enabled state and server-side UserId or group permission. Review server output for the rejection reason while testing.
Multiple tags appear in the wrong order
Higher priorities appear first. If two tags share a priority, their IDs are ordered alphabetically. Give important staff tags distinct priorities for predictable results.
Bubble overrides do not appear
Enable Bubble chat globally and enable Bubble custom style on the tag. When tags stack, only the strongest tag controls the bubble override. Blank colors and -1 values inherit global settings.
System messages do not appear
Call SendMessage from a server Script, use an enabled message-type ID, and confirm the generated client and Bridge are present. If targeting selected players, pass a Player or an array of Players.
Runtime is marked dirty after a plugin update
This is expected when the generated runtime version no longer matches the plugin. In Edit mode, use Repair generated runtime, review the configuration and Save.
Production checklist
Before publishing:
- Back up the place and current Astro Chat configuration.
- Confirm the product release is 1.0 and the included plugin/runtime build is 1.6.1.
- Confirm the experience uses TextChatService.
- Apply or repair the complete generated runtime in Edit mode.
- Configure every non-fallback tag’s access rule.
- Test authorized and unauthorized accounts.
- Test single-tag or stacked-tag ordering as configured.
- Test the chat window, input bar, channel tabs and bubble chat on desktop and mobile layouts.
- Test each message type through a server Script.
- Check for competing TextChatService callbacks.
- Save explicitly and verify the runtime is reported as up to date.
- Publish to a test universe before production.
After launch, review tag access whenever staff roles, group ranks or entitlements change. Re-run the checklist after plugin updates or changes to other chat-related scripts.
