ApexDocs is a non-opinionated documentation generator for Salesforce Apex classes. It can output documentation in Markdown format, which allows you to use the Static Site Generator of your choice to create a documentation site that fits your needs, hosted in any static web hosting service.
ApexDocs generates Markdown files, which can be integrated into any Static Site Generation (SSG) engine, (e.g. Jekyll, Vitepress, Hugo, Docosaurus, etc.) to create a documentation site that fits your needs.
This gives you the flexibility to create beautiful sites by leveraging your preferred SSG engine, which usually provides a wide range of themes, dark mode support, and other features out of the box.
These are examples of documentation sites generated using Vitepress.
Head over to the examples/vitepress
folder to see the code.
The extra flexibility also lets you integrate the output documentation with your existing documentation site, allowing you to match the look and feel of your existing site.
OpenApi REST definitions can be visualized using a tool like ReDoc, Swagger UI, or any other OpenApi viewer.
This repo contains several other example implementations in the examples
directory, showcasing how to integrate
with different tools.
Here are some live projects using ApexDocs:
- Generate documentation for Salesforce Apex classes as Markdown files
- Generate an OpenApi REST specification based on
@RestResource
classes - Generate a changelog based on the differences between two versions of your Salesforce Apex classes
- Support for grouping blocks of related code within a class
- Support for ignoring files and members from being documented
- Namespace support
- Configuration file support
- Single line ApexDoc Blocks
- Custom tag support
- And much, much more!
npm i -g @cparra/apexdocs
Run the following command to generate markdown files for your global Salesforce Apex classes and custom objects:
apexdocs markdown -s force-app
Run the following command to generate an OpenApi REST specification for your Salesforce Apex classes
annotated with @RestResource
:
apexdocs openapi -s force-app
Run the following command to generate a changelog for your Salesforce Apex classes:
apexdocs changelog --previousVersionDir force-app-previous --currentVersionDir force-app
markdown
Flag | Alias | Description | Default | Required |
---|---|---|---|---|
--sourceDir |
-s |
The directory where the source files are located. | N/A | Yes |
--targetDir |
-t |
The directory where the generated files will be placed. | docs |
No |
--scope |
-p |
A list of scopes to document. Values should be separated by a space, e.g --scope global public namespaceaccessible. | global |
No |
--defaultGroupName |
N/A | The default group name to use when a group is not specified. | Miscellaneous |
No |
--namespace |
N/A | The package namespace, if any. If provided, it will be added to the generated files. | N/A | No |
--sortAlphabetically |
N/A | Sorts files appearing in the Reference Guide alphabetically, as well as the members of a class, interface or enum alphabetically. If false, the members will be displayed in the same order as the code. | false |
No |
--includeMetadata |
N/A | Whether to include the file's meta.xml information: Whether it is active and and the API version | false |
No |
--linkingStrategy |
N/A | The strategy to use when linking to other classes. Possible values are relative , no-link , and none |
relative |
No |
--customObjectsGroupName |
N/A | The name under which custom objects will be grouped in the Reference Guide | Custom Objects |
No |
The linking strategy determines how ApexDocs will link to other classes in your documentation. For example,
if I have class A
that links through class B
(e.g. through an {@link B}
tag, the @see
tag,
takes it as a param, returns it from a method, etc.), the linking strategy will determine how the link to class B
is
created.
These are the possible values for the linkingStrategy
flag:
relative
Create a relative link to the class file.
So if both classes are in the same directory, the link will be created as
[B](B.md)
.
If the classes are in different directories, the link will be created as [B](../path/to/B.md)
no-link
Does not create a link at all. The class name will be displayed as plain text.
none
Does not apply any kind of logic. Instead, the links will be determined by the path to the file
from the root of the documentation site OR by whatever path you have returned in the transformReference
hook
for the file.
apexdocs markdown -s force-app -t docs -p global public namespaceaccessible -n MyNamespace
openapi
Flag | Alias | Description | Default | Required |
---|---|---|---|---|
--sourceDir |
-s |
The directory where the source files are located. | N/A | Yes |
--targetDir |
-t |
The directory where the generated files will be placed. | docs |
No |
--fileName |
N/A | The name of the OpenApi file. | openapi.json |
No |
--namespace |
N/A | The package namespace, if any. This will be added to the API file Server Url. | N/A | No |
--title |
N/A | The title of the OpenApi file. | Apex REST API |
No |
--apiVersion |
N/A | The version of the API. | 1.0.0 |
No |
apexdocs openapi -s force-app -t docs -n MyNamespace --title "My Custom OpenApi Title"
changelog
Flag | Alias | Description | Default | Required |
---|---|---|---|---|
--previousVersionDir |
-p |
The directory location of the previous version of the source code. | N/A | Yes |
--currentVersionDir |
-t |
The directory location of the current version of the source code. | N/A | Yes |
--targetDir |
-t |
The directory location where the changelog file will be generated. | ./docs/ |
No |
--fileName |
N/A | The name of the changelog file to be generated. | changelog |
No |
--scope |
N/A | The list of scope to respect when generating the changelog. | ['global'] | No |
--skipIfNoChanges |
N/A | Whether to skip generating the changelog if there are no changes. | true |
No |
apexdocs changelog -p force-app-previous -t force-app
You can also use a configuration file to define the parameters that will be used when generating the documentation.
Configuration files are the main way to integrate the generated documentation with the Static Site Generator of your choice and your build process, as well as configuring any custom behavior and the output of the generated files.
Apexdocs uses cosmiconfig to load the configuration file, which means it supports the following formats (plus anything else supported by cosmiconfig):
- A
package.json
property, e.g.{ "apexdocs": { "sourceDir": "src", "targetDir": "docs" } }
- A
.apexdocsrc
file, written in YAML or JSON, with optional extensions:.yaml/.yml/.json/.js
- An
apexdocs.config.js
(or.mjs
) file that exports an object - A
apexdocs.config.ts
file that exports an object
The configuration file should be placed in the root directory of your project.
Note that when using a configuration file, you can still override any of the parameters by passing them through the CLI.
When defining a configuration file, it is recommended to use ES modules syntax. The config file should default
export an object with the parameters you want to use.:
export default {
sourceDir: 'force-app',
targetDir: 'docs',
scope: ['global', 'public'],
...
}
Every property in the configuration file is optional, and if not provided, either the value provided through the CLI will be used, or the default value will be used.
Using the defineMarkdownConfig
(or the defineOpenApiConfig
for OpenApi documentation)
helper will provide Typescript-powered intellisense
for the configuration file options. This should work with both Javascript and Typescript files.
import { defineMarkdownConfig } from "@cparra/apexdocs";
export default defineMarkdownConfig({
sourceDir: 'force-app',
targetDir: 'docs',
scope: ['global', 'public'],
...
});
You might want to generate different types of documentation using a single command. For example, if you are releasing a new version of your project, you might want to generate updated documentation Markdown files, and at the same time generate a changelog listing everything new.
You can do this by providing a configuration file that exports a configuration object which keys are the type of documentation you want to generate.
import { defineMarkdownConfig, defineChangelogConfig } from '@cparra/apexdocs';
export default {
markdown: defineMarkdownConfig({
sourceDir: 'force-app',
targetDir: 'docs',
scope: ['global', 'public'],
...
}),
changelog: defineChangelogConfig({
previousVersionDir: 'force-app-previous',
currentVersionDir: 'force-app',
targetDir: 'docs',
scope: ['global', 'public'],
})
};
Then you only need to run the top level apexdocs
command, and it will generate both types of documentation.
Note that you can still run the individual commands if you only want to generate one type of documentation by
providing the subcommand, e.g apexdocs markdown
or apexdocs changelog
.
Any pattern included in the .forceignore
file will be excluded from the documentation.
Additionally, you can exclude one or multiple files from being documented by providing a list of glob patterns to
the exclude
property in the configuration file.
import { defineMarkdownConfig } from "@cparra/apexdocs";
export default defineMarkdownConfig({
sourceDir: 'force-app',
targetDir: 'docs',
scope: ['global', 'public'],
exclude: ['**/MyClass.cls', '**/MyOtherClass.cls'],
...
});
Note: Only works for Markdown documentation.
You can exclude tags from appearing in the documentation by using the excludeTags
property in the configuration file,
which allow you to pass a list of tags that you want to exclude from the documentation.
import { defineMarkdownConfig } from "@cparra/apexdocs";
export default defineMarkdownConfig({
sourceDir: 'force-app',
targetDir: 'docs',
scope: ['global', 'public'],
excludeTags: ['internal', 'ignore'],
...
});
When defining a .js
or .ts
configuration file, your object export can also make use
of different hooks that will be called at different stages of the documentation generation process.
All hooks can be async functions, allowing you to make asynchronous operations, like calling an external API.
There are hooks for both Markdown and Changelog operations (but not for OpenApi).
Allows changing the Allows changing the frontmatter and content of the reference guide, or even if creating a reference guide page should be skipped.
Type
type TransformReferenceGuide = (referenceGuide: ReferenceGuidePageData) => Partial<ReferenceGuidePageData> | Skip | Promise<Partial<ReferenceGuidePageData> | Skip>;
Example: Updating the frontmatter
export default {
transformReferenceGuide: (referenceGuide) => {
return {
// The frontmatter can either be an object, of the frontmatter string itself
frontmatter: { example: 'example' }
};
}
};
Example: skipping the reference guide
// The skip function is imported from the package
import { defineMarkdownConfig, skip } from "@cparra/apexdocs";
export default defineMarkdownConfig({
transformReferenceGuide: (referenceGuide) => {
return skip();
}
});
The main purpose of this hook is to allow you to skip the generation of specific pages,
by returning a filtered array of DocPageData
objects.
If you want to modify the contents or frontmatter of the docs, use the transformDocPage
hook instead.
Type
type TransformDocs = (docs: DocPageData[]) => DocPageData[] | Promise<DocPageData[]>
Example
export default {
transformDocs: (docs) => {
return docs.filter(doc => doc.name !== 'MyClass');
}
};
Allows changing the frontmatter and content of the doc page.
Type
type TransformDocPage = (
doc: DocPageData,
) => Partial<ConfigurableDocPageData> | Promise<Partial<ConfigurableDocPageData>>
Example
export default {
transformDocPage: (doc) => {
return {
frontmatter: { example: 'example' }
};
}
};
Allows changing where the files are written to and how files are linked to each other.
Type
type TransformReference = (
reference: DocPageReference,
) => Partial<ConfigurableDocPageReference> | Promise<ConfigurableDocPageReference>;
Example
export default {
// Notice how we are setting the linking strategy to none, so that nothing is done
// to the links by the tool when it tries to link to other classes
linkingStrategy: 'none',
transformReference: (reference) => {
return {
// Link to the class in Github instead to its doc page.
referencePath: `https://github.com/MyOrg/MyRepo/blob/main/src/classes/${reference.name}.cls`
};
}
};
Allows changing the frontmatter and content of the changelog page.
Type
type TransformChangeLogPage = (
changelog: ChangeLogPageData,
) => Partial<ChangeLogPageData> | Promise<Partial<ChangeLogPageData>>
// Supporting types
type ChangeLogPageData = {
source: SourceChangelog;
frontmatter: string | Record<string, any>;
content: string;
outputDocPath: string;
};
type SourceChangelog = {
fileChanges: FileChange[];
};
type FileChange = {
name: string;
fileType: 'apex' | 'customobject';
changeType: 'added' | 'removed' | 'changed';
changes?: {
addedMethods?: string[];
removedMethods?: string[];
addedFields?: string[];
removedFields?: string[];
addedProperties?: string[];
removedProperties?: string[];
addedCustomFields?: string[];
removedCustomFields?: string[];
addedSubtypes?: string[];
removedSubtypes?: string[];
addedEnumValues?: string[];
removedEnumValues?: string[];
};
};
Example
export default {
transformChangeLogPage: (changelog) => {
return {
frontmatter: { example: 'example' }
};
}
};
If you are just interested in the Apex parsing capabilities, you can use the standalone Apex Reflection Library which is what gets used by this library behind the scenes to generate the documentation files.
If you would like to use the processing capabilities of ApexDocs directly from Javascript/Typescript
instead of using the CLI, you can import the process
function from the library.
import { process } from '@cparra/apexdocs';
process({
sourceDir: 'force-app',
targetDir: 'docs',
scope: ['global', 'public'],
...
});
If using Typescript, ApexDocs provides all necessary type definitions.
See the wiki for an in-depth guide on how to document your Apex code to get the most out of ApexDocs.
ApexDocs can also generate OpenApi REST definitions for your Salesforce Apex classes annotated with @RestResource
.
See the wiki for more information.