Developers
Learn how to integrate SCFD Changelog into your own resources.
SCFD Changelog is designed to be lightweight, standalone, and easy to integrate into your own resources. It exposes a small collection of client and server exports that allow other resources to open the changelog, retrieve release information, and react to new updates.
The resource is entirely server-authoritative. Changelog parsing, validation, and webhook publishing all occur on the server before sanitized data is sent to clients.
Resource Architecture
The resource is split into several components.
client/
Handles NUI communication and exports.
server/
Parses CHANGELOG.md, validates releases and handles webhook publishing.
shared/
Shared constants, logging and utilities.
web/
React + Tailwind + shadcn/ui NUI.
data/
Configuration.
locales/
ox_lib localization.Client Exports
Client exports allow your resources to interact with the changelog UI.
showChangelog
Opens the newest available changelog.
exports.scfd_changelog:showChangelog()Example
RegisterCommand("updates", function()
exports.scfd_changelog:showChangelog()
end)showRelease
Opens a specific release.
exports.scfd_changelog:showRelease("3.0.0")Returns true if the release exists.
Example
local success = exports.scfd_changelog:showRelease("2.5.0")
if not success then
print("Release not found.")
endhasUnreadChangelog
Returns whether the player has viewed the newest version.
local unread = exports.scfd_changelog:hasUnreadChangelog()Returns:
booleanExample:
if exports.scfd_changelog:hasUnreadChangelog() then
print("Player has unread updates.")
endisOpen
Returns whether the changelog UI is currently visible.
local open = exports.scfd_changelog:isOpen()Returns:
booleansetEnabled
Temporarily enables or disables automatic opening.
exports.scfd_changelog:setEnabled(false)Useful when another resource is displaying onboarding screens or character selection.
Example:
exports.scfd_changelog:setEnabled(false)
-- Character creator...
exports.scfd_changelog:setEnabled(true)Server Exports
Server exports provide access to parsed changelog data.
getChangelog
Returns every parsed release.
local releases = exports.scfd_changelog:getChangelog()Returns:
tablegetRelease
Returns a specific release.
local release = exports.scfd_changelog:getRelease("3.0.0")Returns:
table | nilExample:
local release = exports.scfd_changelog:getRelease("3.0.0")
if release then
print(release.title)
endgetReleaseMetadata
Returns only a release's metadata.
local metadata = exports.scfd_changelog:getReleaseMetadata("3.0.0")Useful if you only need:
- title
- summary
- author
- important flag
without loading the entire release.
getCategories
Returns every discovered category.
local categories = exports.scfd_changelog:getCategories()Example output:
{
"General",
"Police",
"EMS",
"Economy",
"UI"
}reloadChangelog
Reloads and reparses CHANGELOG.md.
exports.scfd_changelog:reloadChangelog()Useful during development.
Reloading does not automatically reopen the changelog for connected players.
Release Object
Each parsed release has the following structure.
{
version = "3.0.0",
date = "July 18, 2026",
metadata = {
title = "Summer Update",
summary = "...",
author = "Scuffed Labs",
important = true,
},
sections = {
{
name = "Added",
entries = {
{
category = "Police",
text = "Added evidence lockers."
}
}
}
}
}Metadata Object
Metadata contains information displayed at the top of the changelog.
metadata = {
title = "...",
summary = "...",
author = "...",
important = true
}All fields are optional.
Section Object
Every release contains one or more sections.
{
name = "Fixed",
entries = {
...
}
}Supported section names are:
- Added
- Changed
- Fixed
- Removed
- Deprecated
- Security
Entry Object
Each individual changelog entry contains:
{
category = "Police",
text = "Added new armory locations."
}If no category is specified in the Markdown file:
category = "General"is assigned automatically.
Version Detection
The current release version is automatically read from your resource metadata.
Example:
version "3.0.0"Changing the resource version automatically causes the newest release to become unread for every player.
No manual configuration is required.
Player View History
Player history is stored using FiveM KVP storage.
The resource tracks:
- Latest viewed version
- Automatic display status
This allows:
- once-per-version display
- reopening previous releases
- seamless updates
Webhook Publishing
Webhook publishing is handled entirely on the server.
The client never receives:
- webhook URLs
- publish commands
- webhook configuration
Only sanitized release information is sent to Discord.
Security
SCFD Changelog validates every release before it reaches the client.
Validation includes:
- Metadata parsing
- Section validation
- Entry validation
- Character sanitization
- HTML stripping
- Invalid release detection
Malformed releases are ignored instead of being displayed.
React Integration
The NUI communicates using standard FiveM NUI callbacks.
Developers rebuilding the interface should use the provided helper utilities.
Recommended helpers include:
fetchNui()useNuiEvent()isEnvBrowser()
These simplify local development and improve compatibility with browser mode.
Browser Development
The React application supports running outside FiveM for development.
When running in browser mode:
- Mock data is automatically loaded.
- NUI events are simulated.
- Live React development is supported.
This allows rapid UI iteration without restarting your server.
Best Practices
Use Exports
Prefer using exports instead of directly triggering internal events.
Avoid Editing Parsed Data
Treat release data as read-only after it has been parsed.
Keep Releases Small
Large changelogs are automatically paginated, but concise releases provide a better user experience.
Reload During Development
Use reloadChangelog() instead of restarting the resource while editing release notes.
API Stability
All documented exports are considered part of the public API and are intended to remain stable across future releases.
Internal events, functions, and implementation details should be considered private and may change without notice.
