Formerly Sincronia β see Migrating from Sincronia if you're upgrading an existing project.
Dovetail is a tool for managing ServiceNow code in a more modern way. It allows you to:
- Store scoped app code in GitHub in an editable way.π (Looking at you studio source controlπ)
- Run your code through build pipelines that enable you to write modern JavaScript and use modern development tools such as TypeScript, Babel, and Webpack. π
- Take control of your development process in ServiceNow! πͺ
Check out the tutorial videos!
Table of Contents
In order to use Dovetail, you will need:
- Node.js version 20 LTS or later
- If you are on Windows you will also need :
- Windows subsystem for Linux installed (Ubuntu should work fine)
- Preferably updated to version 1903+ (Previous versions untested/not working)
- (Optional) Preferably Windows Terminal installed for rendering the text from the tool
- Create a folder to store the scoped app code.
- In a terminal, run
npm initinside the newly created folder and follow the instructions to set up your node module. - Import the scoped app from source control into your instance.
- Install
@tenonhq/dovetail-core
npm i -D @tenonhq/dovetail-core- Initialize your Dovetail project
npx dove init- Configure your project!
- OPTIONAL BUT HIGHLY RECOMMENDED Once your project is configured the way you like, you can commit and push it to a git repository for superior tracking and version control! Make sure to create a
.gitignorefile and ignorenode_modulesand.envbecause you really don't want those files in your repository. - Start watch mode and start working! Every time you save a file that is tracked by Dovetail, it will be built with your ruleset and the result will be placed in ServiceNow!
npx dove watchDovetail takes a two-pronged approach to managing your ServiceNow scoped app. Architecture, creation of records, deletion of records, metadata and other ServiceNow objects besides your actual source code will be managed normally. Your source code itself will be managed inside of your Dovetail project.
Dovetail has a few basic commands to help you get the job done
| Command | Aliases | Description | Usage |
|---|---|---|---|
refresh |
r |
Refreshes the dove.manifest.json file and downloads all new files created in ServiceNow since the last refresh. Does not override existing file contents. |
npx dove refresh |
watch |
w, watchAllScopes |
Watches files for changes, then builds and pushes them to the corresponding record. Multi-scope by default. Only works on files in the manifest file. | npx dove watch |
init |
none | Walks you through creating a basic Dovetail project. This is the recommended way to create a Dovetail project from scratch. | npx dove init |
push |
none | Builds and pushes all files in your local Dovetail project to the ServiceNow instance in your .env file |
npx dove push |
download <scope> |
none | Downloads the specified scoped app, overwriting all local files in the way. Only use this if you know what you are doing! | npx dove download my_test_app |
build |
none | Builds the local Dovetail project and stores the files locally | npx dove build |
deploy |
none | Deploys the files in the build folder to the ServiceNow instance. | npx dove deploy |
status |
none | Lists the connected ServiceNow instance, scope, and user | npx dove status |
When using the push and build commands you can specify a branch to compare changes against using the --diff option. When using diff with build, Dovetail will build all files from the source folder but will create a dove.diff.manifest.json file that tracks changes for deploy.
npx dove build --diff masterWhen you download your source code using Dovetail, it creates a folder structure that goes as follows:
project_folder/
src/
table_name/
record_name/
field_name.extension
Records are shown as folders because there are times where there are multiple code files per record. This makes it very important that you never have records with the exact same display value in the same table! If you do, then you will notice issues building your files to the right record in ServiceNow.
This is the configuration file for Dovetail. Learn More
Keeps track of all ServiceNow files that are watched by Dovetail. Do not manually modify it
Tracks changed files for build and deploy commands when using diff option.
Stores login credentials and and the instance URL. Do not commit this to git
When you download your source code using Dovetail, you are effectively 'taking control' of that code. Once the code is in your project, you no longer want to edit it directly in ServiceNow! This is why putting your code into source control is highly recommended. Anything else besides code, such as tables, configuration of script records, metadata, etc. must still be tracked in ServiceNow and passed along with your preferred method of moving ServiceNow architecture
Modern javascript development workflows are asymmetric, meaning that the source code you write is usually not the code that gets executed. It is built using various tools and compiled/transpiled into some more compatible or smaller javascript code that is run by browsers or node environments.
Dovetail takes advantage of this same principle by allowing you to leverage some of those same tools. This means that you will no longer be able to store your source code directly in ServiceNow, instead you will have a local version of your source code that gets built and the result of that build will be put into ServiceNow.
EXAMPLE
Let's say I want to develop using TypeScript. Once I have the right plugin configuration for my needs, this Typescript file:
// Example/script.ts
class Example {
constructor(message: string) {
gs.info(message);
}
sayHello() {
gs.info("Hello, Dovetail!");
}
}becomes
// ServiceNow `Example` script include.
"use strict";
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
var Example =
/*#__PURE__*/
(function () {
function Example(message) {
_classCallCheck(this, Example);
gs.info(message);
}
_createClass(Example, [
{
key: "sayHello",
value: function sayHello() {
gs.info("Hello, Dovetail!");
},
},
]);
return Example;
})();File extensions are typically only one short blurb (e.g. .js, .css, etc.). When you use Dovetail, you may find that you want to treat one .js file differently than another. That's where extensions can become more powerful! You could create an extension in your project such as .server.js and .client.js which you could combine with the rules configuration of Dovetail to have two different build pipelines. You could use Webpack for client scripts and Babel for server scripts! Pretty cool huh?
As long as the main filename stays the same, you can add as many extensions as you want.
EXAMPLE
script.js becomes script.servicenow.js or script.ts or script.what.ever.you.want.js
Dovetail aims to be as configurable as possible. To do that, it creates a special javascript file in your project directory called dove.config.js. It's contents will look something like this:
module.exports = {
// Directory where your source files will be kept and will be watched by Dovetail
// during development.
sourceDirectory: "src",
//Directory where local builds will be stored
buildDirectory: "build",
// This is where you will configure your plugins. You match based on plugins.
// Order your rules by MOST SPECIFIC extension first! The first match is the
// only one that gets executed.
rules: [],
// === INCLUDES/EXCLUDES apply on top of the default config! See more below ===
// Tables/fields to exclude (AKA not download or track) from Dovetail
excludes: {},
// Tables/fields to explicitly include in your Dovetail project.
// Can override excludes if necessary.
includes: {},
//How often dovetail will refresh the manifest in development mode
refreshInterval: 30,
};If you find that your config is getting too large, you can use typical nodejs techniques for splitting it into smaller modules and loading them into the dove.config.js.
OR
When you first set up your project, you may notice you may have more files than you want to manage or some files are missing. This can be easily resolved by tweaking your includes and excludes section of your dove.config.js. Dovetail attempts to establish sane defaults for these values here.
If you think there is something wrong with the default setup, feel free to submit a pull request! ππ
The excludes and includes sections in your dove.config.js act as additions to that default setting. You can override parts of it or turn parts of it off.
Once you have updated your includes and excludes, run npx dove refresh to load the new files and update the manifest. You will have to manually delete any newly excluded tables/fields.
// dove.config.js
module.exports = {
excludes: {
// Turns off the default exclusion of the `sys_scope_privilege` table
sys_scope_privilege: false,
// Excludes everything from the `my_cool_table` table
my_cool_table: true,
// Excludes the `cool_script` field specifically from the `new_cool_table` table.
// Other valid fields will be included.
new_cool_table: {
cool_script: true,
},
},
includes: {
// Turns off the default inclusion of the `content_css` table
content_css: false,
// Explicitly includes the `sys_report` table. Overrides any excludes on the
// same table.
sys_report: true,
// Explicitly pulls in the `neat_script_field` as a `js` file in spite of whatever
// type of field it might be in ServiceNow. Useful for text fields that
// represent code.
special_code_table: {
neat_script_field: {
type: "js",
},
},
},
};Plugins are where the true πͺ POWER πͺ of Dovetail comes from! The rules section is used to configure plugins. When configuring plugins, Make sure to always put your rules in the order you want them matched! The first rule that gets matched will be the only one that runs!
// dove.config.js
module.exports = {
rules: [
{
// The match argument is a regular expression that will match on your desired files
// The order matters, so put your most specific rules first!
// If there is a file that ends in `.secret.ts` it will match here and
// NO PLUGINS WILL BE RUN
match: /\.secret\.ts$/,
plugins: [],
},
{
// If there are just generic TypeScript files that have no other extension, they will
// match on this rule instead.
match: /\.ts$/,
// List of plugins to run on the matched files. Each plugin will run in order.
// THE RESULT OF THE PREVIOUS PLUGIN WILL BE PASSED TO THE NEXT PLUGIN so make
// sure they are in the right order!
plugins: [
{
// The name of the plugin, it is the same as the name of the NPM package of
// the plugin.
name: "@tenonhq/dovetail-typescript-plugin",
// Options to pass to the plugin. This will be defined by the plugin itself.
// In this case, we are telling the typescript plugin to only type check and
// not transpile.
options: {
transpile: false,
},
},
],
},
],
};This is a relatively new feature and potentially subject to change
The tableOptions section allows for special setups on any table. Example:
// dove.config.js
module.exports = {
// ...
tableOptions: {
some_table: {
// sets the field used for the record folder name
displayField: "some_field",
// Allows to de-duplicate records based on certain fields
differentiatorField: "sys_id",
// can be an array, if there isn't a value in a field, it moves to the next one
differentiatorField: ["some_field", "sys_id"],
// an encoded query to filter records by
query: "some_field=test",
},
},
};Note on differentiatorField
This feature will currently put a colon in the filename. This will break the Windows filesystem. Use at your own risk.
Deleting something in Dovetail is relatively simple. Just follow these steps:
- Turn off dev mode if you are currently running Dovetail
- Delete the record in ServiceNow
- Run
npx dove refresh - Remove the files from your project
Why is this not automatic? Deleting files can be a dangerous game and it should be a deliberate action!
- Turn off dev mode if you are currently running Dovetail
- Create the record in ServiceNow
- Run
npx dove refreshand the files will get created automatically π
For an example project, we uploaded the server side code for Dovetail! Feel free to contribute to that code if you'd like π
| Name | Description |
|---|---|
| @tenonhq/dovetail-babel-plugin | Runs Babel on .js/.ts files |
| @tenonhq/dovetail-prettier-plugin | Prettifies your output files using Prettier |
| @tenonhq/dovetail-sass-plugin | Runs the Sass compiler on your files |
| @tenonhq/dovetail-typescript-plugin | Type checks and compiles TypeScript files |
| @tenonhq/dovetail-webpack-plugin | Creates Webpack bundles with your files |
| @tenonhq/dovetail-eslint-plugin | Runs ESLint on your files on build |
Dovetail is a fork of Sincronia, originally authored by Nuvolo and the Sincronia contributors. TenonHQ has maintained and extended the project since 2024 β modifications are recorded in this repository's Git history and in CHANGELOG.md. The project remains licensed under the GNU General Public License v3 (see LICENSE).
If you're upgrading an existing Sincronia project, see docs/migrate-from-sincronia.md. The npm packages have been republished under the @tenonhq/dovetail-* namespace and the CLI binary is now dove (sinc remains as a deprecated alias for one minor release).

