Almost all of the content for site now lives under the astro directory and is built by astro. You can run the site by cd-ing into the astro directory and running npm scripts from there or by running savant targets from the top level.
The build targets are
npm run dev
orsb watch
: starts the astro dev server. Will render each page on demand and watch for file changes. Does not run the full buildnpm run start
orsb server
: runs a full build except for the compression step and will serve the whole site. Useful if you need test search or the build process.npm run dev-build
orsb compile
: runs the build step minus the compression but does not serve the file
Here are some guidelines to follow when writing documentation (everything under docs), articles (everything under articles), and blogs blog.
- Capitalize all domain objects, especially when working the object's API in which it is created and updated in FusionAuth.
For example, see the API Key APIs description for
apiKeyId
, where API Key is capitalized:The unique Id of the API Key to create. If not specified a secure random UUID will be generated.
- If referring to something that exists as a domain object in FusionAuth, but you are not explicitly referring to an object being created/updated in FusionAuth, use lowercase. Here are some examples:
To allow users to log into and use your application, you’ll need to create an Application in FusionAuth.
- From the Link API, note the difference between a FusionAuth User and a 3rd party user:
This API is used to create a link between a FusionAuth User and a user in a 3rd party identity provider. This API may be useful when you already know the unique Id of a user in a 3rd party identity provider and the corresponding FusionAuth User.
- Do not manually wrap long lines. Use the soft wrap in your editor to view while editing.
- Use
Id
instead ofID
orid
when describing a unique identifier - Use
admin UI
instead ofAdmin UI
when writing about the admin user interface. - Use
logged in
instead oflogged-in
log in
is the verb,login
is the noun- Use
UserInfo
instead ofUserinfo
- Don't abbreviate FusionAuth, use the full name.
- References to
http://127.0.0.1
should be updated tohttp://localhost
. Remove hyperlinks tolocalhost
. - Always provide an alt text for images. It should always be a full sentence describing the content of the image.
- In general, put screenshot images after the text describing the image. That is "This functionality....\n\n". However, when describing fields for screens, as in the core concepts section, put the screenshot first.
- If possible use an SVG for images. Otherwise, a PNG that has been properly minified is acceptable.
- Never use the term GUID, it's always UUID. If you mention any, display them in
8-4-4-4-12
format:631ecd9d-8d40-4c13-8277-80cedb8236e3
- When introducing a code snippet, don't use a : (colon). Instead, just use verbiage before it. "The code to exchange the token is similar to below."
- Prefer 'You' to 'We'. 'Let's' is acceptable.
- All code snippets within any documents should have indenting formatted to 2 spaces.
- Code captions should have the first letter of every word capitalized: This Code Is The Best.
- All image captions should be one or more complete sentences.
- Use the oxford comma. Apples, bananas, and oranges are my favorite fruits.
- Single spaces should be used instead of double spaces after a period.
- Headers should have the first letter of every word capitalized:
This Is The Header Text
. This is true for all headers (h1, h2, h3, h4). This is also known as Start Case. - When writing, you have access to Asides. Here's an example blog post using an Aside. You can assign the following values to the type:
tip
for tips.note
for things for the user to be aware of.important
for things the user should pay attention to.warn
for dangerous actions like deleting a tenant. - For links, don't use the absolute URL for the FusionAuth website (https://fusionauth.io), only relative URLs. This allows us to deploy to our local and staging environments and not get sent over to prod.
- If you have a list element containing more than one paragraph, indent the second paragraph by the same amount as the start of the text in the first paragraph to make sure that it renders correctly.
-
Don't use complex breadcrumbs styling in docs. Use
->
. Use the Breadcrumb component. Breadcrumbs should look like this<Breadcrumb>foo -> bar -> baz</Breadcrumb>
. -
If you are referencing a field in a form or JSON API doc, use the InlineField component:
<InlineField>Issuer</InlineField>
. -
If you are referencing a UI element or button, use the InlineUIElement component:
Click the <UIelement>Ok</UIelement> button
. -
If you are referencing a tab in the UI, use the Breadcrumb component:
On the <Breadcrumb>OAuth</Breadcrumb> tab
. -
When you have a list of values, use this phrase to prefix it: "The possible values are:"
-
When using images that are cropped, add
top-cropped
and/orbottom-cropped
roles as appropriate. Usebox-shadow
only when an image isn't captured in the manner documented below. It's used only when we have screenshots of things that do not have a box shadow and are all white and blend in too much with our white background. No other image classes are needed when creating documentation. -
Include fragments that are shared between different sections of the doc should be stored in the shared directory.
-
All links elements should be fully-qualified and never include a slash at the end (i.e.
[users](/docs/apis/users)
not[users](./users)
) -
If something is new in a version, mark it with something like this:
Available Since Version 1.5.0
If it is inline (for a field), use - AvailableSince
-
If you are deprecating a field, use - DeprecatedSince
-
If you are removing a field, use - RemovedSince
-
The table of contents along the right side is populated by a list of headings extracted from the top level markdown. If you are using nested markdown files with your headings you need to export them into the parent MDX file.
- See Account Portal for an example. See the Astro docs for exported variables and exported properties to see what that is doing.
-
We currently use FontAwesome v6 to render icons, so you can use them to refer to UI buttons, like this:
<IconButton icon="edit" /> <IconButton icon="copy" /> <IconButton icon="fa-search" />
Import the component:
import Icon from 'src/components/icon/Icon.astro';
...
<IconButton name="plus" />
Review the component for all options and icons.
Make descriptions full sentences.
If you want to order pages within a section, use navOrder
. The default value for every page is defined here.
Pages are ordered in the nav within a section in descending order.
navorder: 0
If you want to sort a category to the top of its section, you need to add it to astro/src/tools/docs/categoriesToFloatToTop.json
.
- If you are building a file to include across multiple sections of documentation, make sure you preface the filename with
_
and use dashes to separate words:_login-api-integration
not_login_api_integration
. - You may include both markdown files and astro components as imports in MDX. These are treated as components.
import AccountPortalCore from 'src/content/docs/_shared/_account-portal.mdx';
...
<AccountPortalCore/>
- You can pass
props
to both astro components and mdx components.- For astro components this looks like:
---
const { feature } = Astro.props;
---
{ feature && <><strong>Note:</strong> An Enterprise plan is required to utilize {feature}. </>}
- For mdx it looks like:
---
---
# Getting Help
You can find help for {props.topic} at [help](/help)
- In MDX files you can put some content behind a javascript expression
---
---
{props.showStuff && <>
This is some more content <a href="/home">Home</a>
</>}
- You may need to add a empty tag multi-line content after the expression to indicate that this is a block
- Markdown syntax will not render inside of a block inside of an expression. You must use html there.
- Content passed in the
<slot></slot>
of a component will be passed as rendered markdown. - you may need to coerce a prop into a boolean to use as a conditional for an expression. Such as
{!!props.message && <span>{props.message}</span>}
; - JSON files are their own content collection in astro. You can reference these using the JSON component
- We have an alias mapped in tsconfig that allows you to use absolute references from 'src'. Otherwise, imports must use relative paths.
- If a doc pulls code from an example application, use the RemoteContent. You can also pull sections with tags:
<RemoteContent url="https://raw.githubusercontent.com/FusionAuth/fusionauth-javascript-sdk/main/packages/sdk-react/README.md" tags="forDocSite" />
- We have many APIs which return the same objects either singly (if called with an Id) or in an array (if called without an Id). If you are creating or modifying an API with this, see if you can use the -base pattern that the tenants and applications do to reduce duplicates.
Defaults
is always capitalized.- If a field is required, but only when another feature is enabled, mark it optional rather than required in the API. Then, add a note in the description saying when it is required, like so:
This field is required when <InlineField>theOtherField.enabled</InlineField> is set to true.
- If a feature is only available when using a paid plan, use the PremiumEditionBlurbApi component
<PremiumEditionBlurbApi feature="custom forms" />
fragment for API fields, and PremiumEditionBlurb component for any other location where the feature is mentioned in docs. Only mark the request API fields. - If a feature is only available when using essentials, use the AdvancedEditionBlurbApi component for API fields, and AdvancedEditionBlurb for any other location where the feature is mentioned in docs. Only mark the request API fields with this.
- If a feature is only available when using enterprise, use the EnterpriseEditionBlurbApi component for API fields, and EnterpriseEditionBlurb for any other location where the feature is mentioned in docs. Only mark the request API fields with this.
- If you are working in the
/api/identity-providers
folder there is aREADME
there to help you understand the structure and layout of the documentation for the Identity Providers API. - If a field was deprecated in a version 30 versions ago (deprecated in 1.15, you are now at 1.45), you can remove it from the docs.
This is general layout guidance for APIs that have GET
and POST
options:
## Request section header
GET URLs (could have 1-3 of these, show the most common)
### GET request parameters (path segment)
### GET request parameters (query string)
### GET request headers
POST URLs (only will be one, typically)
### POST request headers
### POST request parameters (path segment)
### POST request body
Example POST request(s)
### Response section header
Response codes
#### Response body
Example response(s)
Follow everything in the Content Style Guidelines
section.
- If updating an blog post, please update the add a meta tag of updated_date:
YYYY-MM-DD
(as opposed to updating the date on the markdown file) - If you have a common component that you want to include, make sure the blog is a
.mdx
file and create a component. Example components - Example blog post using a component - Images should be pulled in using markdown:
![alt text](/path/to/images)
- Images for a blog post should go under /astro/public/img/blogs/` in a directory related to the blog title.
- We use rouge for code formatting. Supported languages are listed here: https://github.com/rouge-ruby/rouge/tree/master/lib/rouge/lexers
- For site navigation, use double quotes: Navigate to "Tenants" and then to the "Password" tab.
- For field names, use double quotes: "Login Identifier Attribute".
- For values, use back ticks:
userPrincipalName
. - Put each blog post into one or more of the known categories. Here's the list. You can separate categories with commas.
- Use tags. They are separated with commas. These are freeform, so feel free to add multiple and choose what works. The first one is what is used to show related posts, unless there's a
featuredTag
value in the front matter. You can learn more about the logic by reviewing the layout. - You can use the
get-images-from-markdown.rb
script to extract images from markdown and store them in a directory. - All references to
stackoverflow.com
should be updated and direct to the community forum athttps://fusionauth.io/community/forum/
- When using an aside in the blog, please use the
nodark="true"
attribute.
- Capitalize the first word.
- Have a period on the end if the list item is a sentence, otherwise don't.
Examples.
I like:
- apples
- bananas
- blueberries
Fruits were domesticated at different times.
- Apples were domesticated 4000 years ago.
- Bananas were domesticated 3000 years before apples.
- Blueberries were not domesticated until around 1900.
- .NET Core
- Azure AD
- CAPTCHA
- client-side
- Connector
- curl
- Docker
- Docker Compose
- ECMAScript
- Elasticsearch
- esport
- first-party
- FusionAuth Cloud
- Google reCAPTCHA
- Identity Provider
- IdP
- Kickstart
- macOS
- multi-factor authentication
- multi-tenancy/multi-tenant
- Node.js
- OAuth and OAuth2
- re-authentication
- self-service
- server-side
- Spring Boot
- third-party
- two-factor
- WebAuthn
- webview
- X.509
- etc
Varies, but you'll always want to
- Open a PR with changes. Tag someone to review it.
- Merge using the GitHub interface or using a squash commit.
Don't push -f
in general. Unless you know what you are doing.
Publishing happens whenever a commit or PR is merged to main
.
When adding screenshots to the documentation, articles or blogs, use a normalized browser window size. The following apple Script should be used to build a consistent Safari browser window.
Note that you must have at least 1100
pixels of screen height. If you do not, your dimensions will be skewed. Go to System Preferences > Display
then choose More Space
or the next selection up from your current selection to ensure you have enough space available.
You will also want to ensure that you do not have scroll bars omni-present, this will affect the UI when taking screenshots. See System Preferences > General > Show scroll bars
and ensure When scrolling
is selected.
Also note that you should be resizing the image down to 1600px wide. If you are resizing up, something is wrong and your images will be fuzzy.
set theApp to "Safari"
# Docs/blog screens
#set appHeight to 1100
#set appWidth to 1080
# Wider UI screens
#set appHeight to 1100
#set appWidth to 1550
# Video
#set appHeight to 1100
#set appWidth to 1550
# Maintenance Mode Screens
#set appHeight to 1100
#set appWidth to 900
tell application "Finder"
set screenResolution to bounds of window of desktop
end tell
set screenWidth to item 3 of screenResolution
set screenHeight to item 4 of screenResolution
tell application theApp
activate
reopen
set xAxis to 640
set yAxis to 360
set the bounds of the first window to {xAxis, yAxis, appWidth + xAxis, appHeight + yAxis}
end tell
- Use light mode when capturing screenshots
- In macOS System Settings > Appearance make sure Allow wallpaper tinting in windows is turned off.
- Make sure you set your
fusionauth-app.runtime-mode
toproduction
unless documenting a feature only available indevelopment
mode. - Use
CMD
+shift
+4
+space
to get the drop-shadow style screenshots - After sizing the window using the AppleScript, do not make the windows smaller in the Y axis.
- If you only want a portion of the screen, crop it. See Application Core Concepts for an example.
- Crop top/bottom if necessary (don't crop sides).
- If you crop the bottom or top, use the
bottom-cropped
ortop-cropped
class on the image. In some cases the class may not be necessary if there is adequate spacing below. When text continues below or right above you will need the class.
- If you crop the bottom or top, use the
- If you crop the image, don't use the
shadowed
role. And vice versa. - Highlight sections using image preview editor
- Highlights should be red rectangle with line weight 5
- To size and compress images without losing too much quality, follow these steps:
- Resize to width of 1600 in Preview.app ( or you can use
sips --resampleWidth 1600 *.png
from the command line) - Crop the image vertically to only display the necessary content.
- Resize to width of 1600 in Preview.app ( or you can use
- Use https://local.fusionauth.io and use the correct kickstart to add the Silicon Valley characters ( https://github.com/FusionAuth/fusionauth-example-kickstart/blob/main/fusionauth/kickstart-development.json )
- Make sure that the same character is used for every screenshot on a page (unless you are demonstrating a view from the admin and also user perspective)
- The shrink-images GitHub Action will call https://tinypng.com/ to compress the images that you commit.
fa-screenshot.sh is located under fusionauth-site/src/
. With this script you can automate following tasks:
- Sizing and moving the Safari window
- Capturing the screenshot
- Resizing the screenshot image
- Moving the image to an appropriate folder
./fa-screenshot.sh -h # for usage info
Gifs take up quite a lot of space: The brew gif was about 5mb, after some custom optimization it was only 2mb
To reduce the space requirements further, a video format is highly recommended and the dominant video format is
webm. Converting a gif to webm is cake: ffmpeg -i terminalizer.gif terminalizer.webm
. ffmpeg will choose all
of the best default settings for you because the format is already specifically for browsers.
The problem is webm is not supported by safari (yet). You will also want to create an mp4 (which isn't always supported by some of the lesser browsers because it uses codecs that require paid licenses inside). You also will have to do some eyeballing on your video because safari is really picky about what it permits.
Example of my brew convert command:
ffmpeg -i render1555538879075.gif -vf scale=744x478 -vsync 2 -pix_fmt yuv420p brew.mp4
-vf scale=
adjusts the scale of the output. The height and width must be divisible by 2!
-vsync 2
makes the framerate variable and is great for terminalizer gifs because there are MANY duplicated frames
that this parameter will drop and significantly reduce your file size (by about half for my brew example)
-pix_fmt yuv420p
changes the pixel format to yuv420p which is the magic sauce that safari wants (this is
also the part that needs a size that is divisible by 2)
We use pagefind to search astro content.
This runs on the astro build. https://pagefind.app/ has more details
In order for it to work you locally you will need to fully run the astro build, it will not work in regular dev mode
npm run start
alternatively if you like savant you can do
sb watch
we build the full site with search for development
We use a combination of URL and frontmatter metadata to determine what documentation section to hold open when you are visiting a doc page. The front matter attributes section
, subcategory
, tertcategory
, and quatercategory
should generally correspond to the url path segments of the page.
In general, set the frontmatter attributes to the correct value when adding a new documentation page.
There are currently nine sections:
- get started: aimed at first time users
- lifecycle: for everyday fusionauth stuff
- customize: making fusionauth your own
- extend: making fusionauth do even MORE stuff
- operate: making fusionauth do the stuff it is supposed to do
- sdks: code we ship that can help you do things with fusionauth
- apis: application programming interfaces
- release notes: see what's new
- reference: general info that the other pages might link to
Please don't add a top level section.
Some sections are better suited to being driven by data. Jekyll makes this easy with lightweight json files in the astro/src/content/json
directory. You can then iterate and filter the data there in various ways in a .astro
file.
- Create a repo. It should have the prefix
fusionauth-example-
and you should add both the owner and devrel teams (as admins) and developer team (as maintainers). - Add a readme and a license (apache2). It's great for the readme to point at the blog post, but you can also update the readme after your post is live.
- Add an entry in https://github.com/FusionAuth/fusionauth-site/blob/main/site/_data/exampleapps.yaml (you can do it on your blog post branch). Note that you can only put an app in one tech group, and that if it is a JavaScript app, use JavaScript, not typescript, as the group name.
This will add the example app to the example apps section in the docs.
Prior to requesting review on a PR, please complete the following checklist.
- If you added or changed an API parameter, ensure you added a version flag.
- When APIs have default values, this is only documented on the request. Do not add it to the response.
- When adding or modifying request or response JSON examples, try to maintain themes and consistently.
- If the create request has a property of
"name": "My application"
, the response should contain this same value. - Try and use real world names and values in example requests/responses. Using name such as
Payroll
for an Application name is more descriptive thanapp 1
and allows the reader to more understand the example.
- If the create request has a property of
- When referencing a field in the description of another field use this syntax:
<InlineField>name</InlineField>
. - Always try and provide a complete description of an API parameter. Brief descriptions that only re-state the obvious are not adeqaute.
- There are times when two fields are optional, because only one of the two are required. In these cases, ensure we explain when the field is required, and when it is optional. There are many examples of this in the doc already for reference.
- Screenshots. Review color, dimensions and clarity. Review A/B to ensure layout has not changed, and the new screenshot is consistent with the previous one.
- In the PR diff, generally speaking the dimensions and file size will be similar, if they are not, something may have changed.
- The screenshot should not look fuzzy. If it does, the compression may be incorrect.
- If you are referring to a navigatable element, use
<Breadcrumb>Tenants</Breadcrumb>
or<Breadcrumb>Tenants -> Your Tenant</Breadcrumb>
. In other words, use it even for singular elements. - If you are referring to a field the user can fill out, use
<InlineField>Authorized Redirect URLs</InlineField>
. - If you are referring to any other UI element, such as a submit button or read-only name, use
<UIelement>Submit</UIelement>
or (on the application view screen)<UIelement>Introspect endpoint</UIelement>
.
TBD
Quickstarts are any pages that are going under /docs/quickstarts that are not on the blog.
See https://github.com/FusionAuth/fusionauth-example-template/blob/main/QUICKSTART-INSTRUCTIONS.md for instructions on building out a quickstart.
We're using Vale to find misspellings and to standardize terms.
The main configuration file is located at .vale.ini
, where we specify file extensions to parse (besides the default .md
one), some custom filters to ignore Astro components and which rules we'll use.
- The rules (or, as Vale calls them, "styles") are located at
config/vale/styles
. - Right now, we're using
write-good
, a collection of simple rules to avoid common mistakes and awkward sentences. - We also have a custom vocabulary at
config/vale/styles/config/vocabularies/FusionAuth/accept.txt
with known words.- Note that this file can use regular expressions to match words in a case-insensitive manner, as described in their docs.
- Anything marked as code (with backticks) is ignored, so if you have a UUID or config string, surrounding it with backticks is a good way to satisfy vale.
There's a GitHub Action that runs Vale on added/modified files when opening a pull request. It'll only cover files located at astro/src/content
and astro/src/components
. It will block merging the PR.
If you have Docker installed, you can lint files by running the command below.
$ cd fusionauth-site
$ docker run --rm -v "$(pwd)/.vale.ini:/etc/.vale.ini" -v "$(pwd)/.github:/etc/.github" -v "$(pwd)/astro:/docs" -w /docs jdkato/vale --config /etc/.vale.ini src/content/path/to/folder/or/file
If you whether choose to install Vale locally, make sure you're at the root folder for this repository and run:
$ cd fusionauth-site
$ vale astro/path/to/folder/or/file
If you want to filter by specific rules, you can also pass a --filter
argument:
$ vale --filter=".Name == 'Vale.Spelling'" astro/path/to/file
Whenever you receive an error, you need to determine if you should:
- Actually fix the word (e.g. if you received an error like "Use 'Id' instead of 'ID'."); or
- Add a known word to
the vocabulary
if it's a language, library, company name, etc. But make sure you have the correct capitalization to avoid having duplicates there; or - In case of custom Astro components, you'd probably need to add a new
TokenIgnores
item in.vale.ini
.
- If a piece of content is technical, it needs a technical review by engineering or devrel.
- Typo fixes don't need review.
- If a piece of content is significant (blog post, guide, article) give it the label
content
and it will be published to a slack channel for marketing awareness.