Named placeholders that resolve at load and deployment time. Pull in file contents, run queries against the target server, and override values per environment without editing the package.
Script tokens are how one schema package becomes ten environments.
Script tokens are how one schema package becomes ten environments. Define a token once, reference it everywhere, override it per environment without touching a script file. The simple form is just two curly braces and a name — but tokens go far beyond that. They can pull in file contents, execute server-side queries to generate values at deployment time, embed entire table schemas as JSON, and surface custom metadata you've attached through Extensions. One feature, many superpowers, all in the free toolset.
The basic form:
{{TokenName}}
Token replacement is case-insensitive. These all resolve to the same value:
SELECT * FROM [{{MainDB}}].dbo.Customers
SELECT * FROM [{{maindb}}].dbo.Customers
SELECT * FROM [{{MAINDB}}].dbo.Customers
Tokens that appear in scripts but have no matching definition are left in place unchanged. There is no silent corruption from an unresolved token.
Tokens are replaced in every place SchemaSmith processes script content, both in JSON expression fields and in .sql files.
BaselineValidationScriptValidationScriptVersionStampScriptBaselineValidationScriptDatabaseIdentificationScriptVersionStampScriptInside table JSON files:
CheckExpression on columnsDefault on columnsExpression on table-level check constraintsFilterExpression on indexes (where supported)ShouldApplyExpression on tables, columns, indexes, foreign keys, check constraints, indexed views, materialized views, and other supported componentsIn every product and template script folder:
| Folder | Scope |
|---|---|
Before Product/, After Product/ |
Product |
Before Scripts/, After Scripts/ |
Template |
Schemas/ |
Template |
DataTypes/ (SQL Server) / Domain Types/, Enum Types/, Composite Types/ (PostgreSQL) / equivalents |
Template |
Functions/, Views/, Procedures/ |
Template |
Triggers/, DDLTriggers/ |
Template |
Table Data/ |
Template |
Any custom script folders defined in Template.json ScriptFolders |
Template |
The exact set of default folders depends on the product's Platform. Anything you add via custom ScriptFolders participates in token resolution exactly the same way as the defaults.
Define tokens in Product.json under ScriptTokens. These are available across every template and product-level script.
{
"Name": "SaasProduct",
"TemplateOrder": ["Registry", "Client"],
"ScriptTokens": {
"RegistryDb": "Registry",
"MigrationVersion": "1.0.1"
}
}
In this example, {{RegistryDb}} resolves to Registry everywhere: in every template's scripts and in every SQL file across the product.
Define tokens in Template.json under ScriptTokens. Template tokens override product tokens with the same key for the duration of that template's execution, and they can also introduce new tokens that only exist inside the template.
Say your Product.json defines a product-wide default for MainDB:
{
"Name": "SaasProduct",
"TemplateOrder": ["Registry", "Reporting"],
"ScriptTokens": {
"MainDB": "ProductionMain"
}
}
Then one of its templates overrides MainDB and adds a new SchemaOwner token that the product never declared:
{
"Name": "Reporting",
"ScriptTokens": {
"MainDB": "ReportingAlias",
"SchemaOwner": "rpt"
}
}
Inside the Reporting template, {{MainDB}} resolves to ReportingAlias instead of the product-scope ProductionMain. The new {{SchemaOwner}} token is available only inside this template; other templates in the same product don't see it.
SchemaSmith adds these tokens automatically. You don't define them; they appear when relevant.
| Token | Value | Available in |
|---|---|---|
{{ProductName}} |
The Name from Product.json |
All product and template scripts |
{{TemplateName}} |
The Name from the current Template.json |
Template scripts only |
{{TableSchema}} |
Full serialized JSON of every table in the current template, with single quotes escaped ('') for safe embedding in SQL string literals |
Template scripts |
{{IndexedViewSchema}} |
Full serialized JSON of every indexed view in the current template (SQL Server) | Template scripts |
{{MaterializedViewSchema}} |
Full serialized JSON of every materialized view in the current template (PostgreSQL) | Template scripts |
{{TableSchema_<TemplateName>}} |
Same as {{TableSchema}} but reaches across templates; name another template explicitly to read its table set |
Any template |
{{IndexedViewSchema_<TemplateName>}} |
Cross-template indexed view JSON | Any template |
{{MaterializedViewSchema_<TemplateName>}} |
Cross-template materialized view JSON | Any template |
{{ObjectScripts_<TemplateName>}} |
Cross-template inventory of programmable object scripts (functions, views, procedures, triggers) | Any template |
{{QueryTokens_<TemplateName>}} |
Cross-template inventory of query-style tokens for sharing | Any template |
{{TableSchema}} is the entire current template's table model serialized as JSON, ready to drop into a stored procedure parameter, a JSON_VALUE/json_each/JSON_TABLE query, or an audit row. Combined with the Custom Properties feature, you can write a single migration script that introspects your table definitions and reads your team's custom metadata at deployment time, without ever leaving SchemaSmith.
The cross-template variants ({{TableSchema_OtherTemplate}}) are how a deployment script in one template can read the schema of another. This unlocks coordination between linked templates without copy-pasting JSON.
Anything you put in an Extensions object on a table component is also available as a token in that component's expression fields. Bare names come from the component's own Extensions; Table.-prefixed names come from the parent table. See Custom Properties for the full mechanism.
{
"Name": "[Orders]",
"Extensions": { "Environment": "Production" },
"Indexes": [
{
"Name": "[IX_Orders_CreatedDate]",
"IndexColumns": "[CreatedDate]",
"ShouldApplyExpression": "SELECT CASE WHEN '{{Table.Environment}}' = 'Production' THEN 1 ELSE 0 END"
}
]
}
That single expression lets the index decide whether to apply itself based on metadata that lives on the parent table, without leaving the schema package or splitting the definition across environment-specific files.
This is where tokens earn their keep. Override product token values in a settings file without modifying the schema package. Same package, different environments, different values.
Add a ScriptTokens section to your tool's settings file (for example, SchemaQuench.settings.json):
{
"Target": {
"Server": "staging-server"
},
"ScriptTokens": {
"RegistryDb": "Registry_Staging",
"MigrationVersion": "1.0.1-rc1"
}
}
Config overrides only apply to tokens that already exist in Product.json. You can't introduce new tokens via configuration alone; the package declares the contract, the environment fills in the values.
Override product token values using environment variables. The naming pattern is:
SmithySettings_ScriptTokens__TokenName=Value
Note the prefix SmithySettings_ (single underscore) and the double underscore __ before the token name. This follows the .NET configuration environment variable convention.
rem Windows (cmd)
set SmithySettings_ScriptTokens__RegistryDb=Registry_CI
set SmithySettings_ScriptTokens__MigrationVersion=1.0.1-ci.42
rem Windows (PowerShell)
$env:SmithySettings_ScriptTokens__RegistryDb = "Registry_CI"
$env:SmithySettings_ScriptTokens__MigrationVersion = "1.0.1-ci.42"
export SmithySettings_ScriptTokens__RegistryDb="Registry_CI"
export SmithySettings_ScriptTokens__MigrationVersion="1.0.1-ci.42"
Like config-level overrides, environment variable overrides only apply to tokens that already exist in Product.json. See Configuration for the full settings-and-environment layering.
When the same token name appears in multiple places, the most specific definition wins. Tokens resolve in layers, from lowest to highest priority:
| Priority | Source | Scope |
|---|---|---|
| 1 (lowest) | Product.json ScriptTokens |
All scripts |
| 2 | Settings file ScriptTokens section |
Overrides matching product keys |
| 3 | Environment variables (SmithySettings_ScriptTokens__*) |
Overrides matching product keys |
| 4 (highest) | Template.json ScriptTokens |
Template scripts only |
Product.json.ProductName token is added.ScriptTokens are merged on top of the resolved product tokens. Template tokens with matching keys win.TemplateName, TableSchema, IndexedViewSchema, and MaterializedViewSchema tokens are added.*_<TemplateName> tokens are added once all templates have loaded.Extensions are merged in per component when the table model is processed.<*File*>, <*Query*>, and the rest) resolve as described above, then the simple {{TokenName}} substitution runs against the final token map.Four scenarios that tie the mechanics together. Each shows the same token shape across SQL Server, PostgreSQL, and MySQL; the SQL dialect on each tab reflects what that engine expects.
A SaaS product uses tokens to manage database names and version stamps across development, staging, and production. Product.json defines the baseline:
{
"Name": "SaasProduct",
"TemplateOrder": ["Registry", "Client"],
"ScriptTokens": {
"RegistryDb": "Registry",
"MigrationVersion": "3.2.0"
}
}
A migration script announces the deployment target using the resolved tokens. The token shape is identical across platforms; only the logging statement differs.
PRINT 'Deploying {{ProductName}} '
+ '{{MigrationVersion}} against '
+ '{{RegistryDb}}';
DO $$ BEGIN
RAISE NOTICE 'Deploying % % against %',
'{{ProductName}}',
'{{MigrationVersion}}',
'{{RegistryDb}}';
END $$;
SELECT CONCAT(
'Deploying {{ProductName}} ',
'{{MigrationVersion}} ',
'against {{RegistryDb}}'
) AS deployment_banner;
For staging, drop a ScriptTokens.RegistryDb override into SchemaQuench.settings.json and ship the same package. For CI, do the same via SmithySettings_ScriptTokens__RegistryDb. The schema package is unchanged across all three environments; only the resolved token values differ.
When templates need to reference sibling schema managed by other templates, product-level tokens keep the references consistent. The token pattern is the same across platforms; what the tokens encode differs because each engine isolates differently. SQL Server and MySQL use separate databases, PostgreSQL uses schemas within one database.
Separate databases, three-part names:
{
"Name": "ECommerce",
"TemplateOrder": ["Catalog", "Orders", "Reporting"],
"ScriptTokens": {
"CatalogDb": "ProductCatalog",
"OrdersDb": "OrderProcessing"
}
}
CREATE OR ALTER VIEW dbo.SalesSummary AS
SELECT o.OrderDate, p.ProductName, o.Quantity, o.Total
FROM [{{OrdersDb}}].dbo.Orders o
JOIN [{{CatalogDb}}].dbo.Products p ON o.ProductId = p.Id;
One database, separate schemas:
{
"Name": "ECommerce",
"TemplateOrder": ["Catalog", "Orders", "Reporting"],
"ScriptTokens": {
"CatalogSchema": "product_catalog",
"OrdersSchema": "order_processing"
}
}
CREATE OR REPLACE VIEW analytics.sales_summary AS
SELECT o.order_date, p.product_name, o.quantity, o.total
FROM {{OrdersSchema}}.orders o
JOIN {{CatalogSchema}}.products p ON o.product_id = p.id;
Separate databases, db-qualified names:
{
"Name": "ECommerce",
"TemplateOrder": ["Catalog", "Orders", "Reporting"],
"ScriptTokens": {
"CatalogDb": "product_catalog",
"OrdersDb": "order_processing"
}
}
CREATE OR REPLACE VIEW `analytics`.`SalesSummary` AS
SELECT o.`OrderDate`, p.`ProductName`, o.`Quantity`, o.`Total`
FROM `{{OrdersDb}}`.`Orders` o
JOIN `{{CatalogDb}}`.`Products` p ON o.`ProductId` = p.`Id`;
PostgreSQL can't join across separate databases natively. The idiomatic pattern is one database with multiple schemas, and SchemaSmith deploys each template against its own schema within that database. Cross-database queries via postgres_fdw or dblink are possible but add setup that's out of scope for most deployments; if you need them, the token approach still works: define foreign-server tokens and reference them the same way.
A migration script that consumes the live table model to drive its own logic. The stored procedure walks the JSON, reads each table's columns and any Extensions.AuditScope you've attached, and emits the right CREATE TRIGGER statements: one declarative source of truth, one runtime that adapts to it.
DECLARE @TableSchema NVARCHAR(MAX) = '{{TableSchema}}';
EXEC dbo.GenerateAuditTriggers @TableSchema;
DO $$
DECLARE
v_table_schema TEXT := '{{TableSchema}}';
BEGIN
CALL public.generate_audit_triggers(v_table_schema);
END $$;
SET @TableSchema = '{{TableSchema}}';
CALL GenerateAuditTriggers(@TableSchema);
A Before migration script needs to know which rows on the server are flagged for the new feature. Point the token at a query file using <*QueryFile*> so the SQL stays readable, and SchemaSmith runs that query once against the actual server you're deploying to before substituting the result into every script that references the token.
The token definition is the same shape across platforms; the query body differs because each engine has its own catalog and quoting style.
Token points at a query file:
{
"ScriptTokens": {
"EnabledTargets": "<*QueryFile*>queries/enabled-targets.sql"
}
}
queries/enabled-targets.sql:
SELECT TargetName
FROM dbo.FeatureFlags
WHERE FlagName = 'NewBilling'
AND Enabled = 1;
Any script in the template:
-- {{EnabledTargets}} resolves to one target per line
PRINT 'Applying billing schema to: {{EnabledTargets}}';
Token points at a query file:
{
"ScriptTokens": {
"EnabledTargets": "<*QueryFile*>queries/enabled-targets.sql"
}
}
queries/enabled-targets.sql:
SELECT target_name
FROM public.feature_flags
WHERE flag_name = 'NewBilling'
AND enabled = true;
Any script in the template:
-- {{EnabledTargets}} resolves to one target per line
DO $$ BEGIN
RAISE NOTICE 'Applying billing schema to: %', '{{EnabledTargets}}';
END $$;
Token points at a query file:
{
"ScriptTokens": {
"EnabledTargets": "<*QueryFile*>queries/enabled-targets.sql"
}
}
queries/enabled-targets.sql:
SELECT `TargetName`
FROM `FeatureFlags`
WHERE `FlagName` = 'NewBilling'
AND `Enabled` = 1;
Any script in the template:
-- {{EnabledTargets}} resolves to one target per line
SELECT CONCAT('Applying billing schema to: ',
'{{EnabledTargets}}') AS status;
Across these four scenarios, the token shape stays constant — {{TokenName}} in scripts, ScriptTokens in JSON — while the SQL dialect and the override surface flex per environment. The same schema package ships unchanged to development, staging, and production; the token layer is where each environment's shape gets expressed.
Last reviewed May 2026 by the SchemaSmith Team.