npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@projectdochelp/s3te

v3.4.9

Published

CLI, render core, AWS adapter, and testkit for S3TemplateEngine projects

Readme

S3TemplateEngine

S3TemplateEngine is a lightweight serverless template engine for people who want to keep writing HTML and still publish through AWS. You write templates and assets, S3TE renders and publishes the static result.

This README is the user guide for the rewrite generation. The deeper implementation specs still live in docs/, but this file is intentionally written for users first.

Table of Contents

Motivation

AWS S3 and CloudFront are a great platform for websites: cheap, fast and low-maintenance. The annoying part usually starts before hosting. You still need reusable HTML, a safe deployment flow, and maybe a way for editors to maintain content without turning your project into a full framework.

S3TemplateEngine is for you if you want to keep writing HTML by hand, but still want to:

  • reuse snippets like headers, navigation and footer blocks
  • generate many pages from one template
  • publish multiple languages or variants from one project
  • keep AWS hosting simple and low-cost
  • optionally let editors maintain content in Webiny
  • deploy without hand-editing Lambda settings or uploading ZIP files yourself

Support

If S3TE saves you time and you want to buy me a tea, you can do that here:

Support me on Ko-fi

If you need help, found a bug, or want to contribute, open an issue or pull request on GitHub.

Concept

S3TE keeps one simple promise: you write source templates, S3TE turns them into static files, AWS serves the result.

S3TE overview

The code bucket receives your variant source files from sourceDir: .html, .part, CSS, JavaScript, images, manifests and everything else you publish for that variant. Files from partDir are uploaded too, but they are treated as reusable template fragments, not as public website assets.

The website bucket contains the finished result that visitors actually receive through CloudFront. That split is what makes incremental rendering, generated pages and safe re-deploys possible.

s3te deploy loads the validated project configuration, packages the AWS runtime, creates or updates one persistent CloudFormation environment stack, creates one temporary CloudFormation deploy stack for packaging artifacts, synchronizes your current source files into the code bucket, and removes the temporary stack again after the real deploy run.

That source sync is not limited to Lambda code. It includes your .html, .part, CSS, JavaScript, images and other project files so the running AWS stack can react to source changes inside the code bucket.

The persistent environment stack contains the long-lived AWS resources such as buckets, Lambda functions, DynamoDB tables, CloudFront distributions and the runtime manifest parameter. The temporary deploy stack exists only so CloudFormation can consume the packaged Lambda artifacts cleanly.

If optional runtime features are enabled in s3te.config.json, S3TE extends the deployed AWS runtime accordingly. sitemap is added to the main environment stack. Webiny is deployed as a separate option stack so retrofitting CMS support does not require CloudFront resources to move with it.

Installation (AWS)

This section is only about the AWS things you need before you touch S3TE. The actual click-by-click screens are best left to the official AWS documentation, because the console changes over time. The goal here is to tell you exactly what S3TE needs from AWS, why it needs it, and which official page gets you there.

| Item | Why S3TE needs it | Official guide | | --- | --- | --- | | AWS account | S3TE deploys into your own AWS account. | Create an AWS account | | Daily-work AWS access | s3te deploy needs credentials that can create CloudFormation stacks and related resources. | Create an IAM user, Manage access keys | | AWS CLI v2 | The S3TE CLI shells out to the official aws CLI. | Install AWS CLI, Get started with AWS CLI | | Domain name you control | CloudFront and TLS only make sense for domains you can point to AWS. | Use your registrar of choice | | ACM certificate in us-east-1 | CloudFront requires its public certificate in us-east-1, and the certificate must cover every alias S3TE will derive for that environment. | Public certificates in ACM | | Optional Route53 hosted zone | Needed only if S3TE should create DNS alias records automatically. | Create a public hosted zone |

  1. Create the AWS account.
  2. Stop using the root user for daily work.
  3. Create one deployment identity for yourself.
  4. Install AWS CLI v2 locally.
  5. Run aws configure and verify it with aws sts get-caller-identity.
  6. Request the ACM certificate in us-east-1.
  7. If you want automatic DNS records, create or locate the Route53 hosted zone.

For a first personal setup, the easiest route is usually one IAM user with console access and access keys. If you already work with AWS Identity Center or another federated login, that is fine too. S3TE uses the standard AWS credential chain.

Installation (VSCode)

This section is only about the local editing experience. S3TE does not require VSCode, but VSCode is the reference editor workflow and the easiest path for most users.

| Tool | Why you want it | Official guide | | --- | --- | --- | | Visual Studio Code | Comfortable editor with integrated terminal and extension support. | VS Code setup overview, Get started with VS Code | | Node.js 20 or newer | Required for the S3TE CLI and local rendering. | Download Node.js |

Open VSCode, open the integrated terminal, and run:

node --version
npm --version

If both commands print a version number, your local machine is ready for S3TE.

Installation (S3TE)

This is the S3TE-specific part. No AWS console links, no editor tutorial, just the steps that actually create and run an S3TE project.

mkdir mywebsite
cd mywebsite
npm install --save-dev @projectdochelp/s3te

With the local package installed, initialize the project like this:

npx s3te init --project-name mywebsite --base-url example.com

You can safely run s3te init more than once. If npm install already created a minimal package.json, s3te init extends it with the missing S3TE defaults and scripts instead of failing. An existing s3te.config.json is completed with missing scaffold defaults, explicit --project-name and --base-url values are refreshed on re-run, and the generated schema file is updated to the current package version. Existing content files and templates stay untouched unless you use --force.

If you want a one-shot scaffold without installing first, and @projectdochelp/s3te is already published on npm, this also works:

npx --package @projectdochelp/s3te s3te init --project-name mywebsite --base-url example.com

The default scaffold creates:

mywebsite/
  package.json
  s3te.config.json
  .github/
    workflows/
      s3te-sync.yml
  app/
    part/
      head.part
    website/
      index.html
  offline/
    content/
      en.json
    schemas/
      s3te.config.schema.json
    tests/
      project.test.mjs
  .vscode/
    extensions.json

The generated .github/workflows/s3te-sync.yml is the default CI path for GitHub-based source publishing into the S3TE code buckets. It is scaffolded once and then left alone on later s3te init runs unless you use --force.

The most important fields for a first deployment are:

{
  "environments": {
    "dev": {
      "awsRegion": "eu-central-1",
      "stackPrefix": "DEV",
      "certificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/replace-me",
      "route53HostedZoneId": "Z1234567890"
    }
  },
  "variants": {
    "website": {
      "languages": {
        "en": {
          "baseUrl": "example.com",
          "cloudFrontAliases": ["example.com", "www.example.com"]
        }
      }
    }
  }
}

route53HostedZoneId is optional. Leave it out if you want to manage DNS yourself.

Use plain hostnames in baseUrl and cloudFrontAliases, not full URLs. If your config contains a prod environment plus additional environments such as test or stage, S3TE keeps the prod hostname unchanged and derives non-production hostnames like this:

  • apex host: example.com -> test.example.com
  • first-level subdomain: app.example.com -> test-app.example.com
  • deeper host: admin.app.example.com -> test-admin.app.example.com

Your ACM certificate must cover the final derived aliases of the environment you deploy. Example:

  • *.example.com covers test.example.com
  • *.example.com also covers test-app.example.com
  • *.example.com does not cover test-admin.app.example.com
  • for deeper aliases like test-admin.app.example.com, add a SAN such as *.app.example.com, the exact hostname, or use a different certificateArn for that environment
npx s3te validate
npx s3te render --env dev
npx s3te test
npx s3te doctor --env dev
npx s3te deploy --env dev

render writes the local preview into offline/S3TELocal/preview/dev/....

doctor --env <name> now also checks whether the configured ACM certificate covers the CloudFront aliases that S3TE derives for that environment. For that check, the AWS identity running doctor needs permission to call acm:DescribeCertificate for the configured certificate ARN.

deploy creates or updates the persistent environment stack, uses a temporary deploy stack for packaged Lambda artifacts, synchronizes the source project into the code bucket, and removes the temporary stack again when the deploy finishes.

After the first successful deploy, use s3te sync --env dev for regular template, partial, asset and source updates when the infrastructure itself did not change.

If you left route53HostedZoneId out of the config, the last DNS step stays manual: point your domain at the created CloudFront distribution after deploy.

Use this step if your team wants GitHub pushes to publish project sources into the S3TE code bucket instead of running s3te sync locally.

s3te init already scaffolded .github/workflows/s3te-sync.yml for that path.

That workflow is meant for source publishing only:

  • it validates the project
  • it reads the selected environment from GitHub and resolves the matching AWS region from s3te.config.json
  • it uploads every configured variant into its own S3TE code bucket
  • the resulting S3 events trigger the deployed Lambda pipeline in AWS

Use a full deploy only when the infrastructure, environment config, or runtime package changes.

GitHub preparation checklist:

  1. Push the project to GitHub together with .github/workflows/s3te-sync.yml.
  2. Make sure GitHub Actions are allowed for the repository or organization.
  3. Run the first real npx s3te deploy --env <name> so the code buckets already exist.
  4. In AWS IAM, create an access key for a CI user that may sync only the S3TE code buckets for that environment.
  5. In GitHub open Settings -> Secrets and variables -> Actions -> Variables.
  6. Add these repository variables:
    • S3TE_ENVIRONMENT Use the exact environment name from s3te.config.json, for example dev, test, or prod. This is required for push-based sync when your project has more than one configured environment.
    • S3TE_GIT_BRANCH optional Use the branch that should trigger the sync job, for example main.
  7. In GitHub open Settings -> Secrets and variables -> Actions -> Secrets.
  8. Add these repository secrets:
    • AWS_ACCESS_KEY_ID
    • AWS_SECRET_ACCESS_KEY
  9. Leave .github/workflows/s3te-sync.yml unchanged unless you want a custom CI flow. The scaffolded workflow already reads:
    • the environment from the manual workflow_dispatch input when provided
    • otherwise from S3TE_ENVIRONMENT
    • otherwise automatically from s3te.config.json when exactly one environment exists
    • the branch from S3TE_GIT_BRANCH or defaults to main
    • the AWS region from s3te.config.json

You do not have to store bucket names, source folders, part folders, or AWS regions in GitHub variables. s3te sync resolves all of that from s3te.config.json.

For projects with multiple environments such as test and prod, the simplest setup is usually one workflow file per target environment, for example:

  • .github/workflows/s3te-sync-test.yml with npx s3te sync --env test
  • .github/workflows/s3te-sync-prod.yml with npx s3te sync --env prod

First verification in GitHub:

  1. Open the Actions tab in the repository.
  2. Select S3TE Sync.
  3. Start it once manually with Run workflow.
  4. Check that the run reaches the Configure AWS credentials, Validate project, and Sync project sources to the S3TE code buckets steps without error.

Where to get the AWS values:

  • AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY In the AWS console open IAM -> Users -> <your-ci-user> -> Security credentials -> Create access key. Save both values immediately. The secret access key is shown only once. AWS documents the credential options and access-key handling here: AWS security credentials, Manage access keys for IAM users.
  • S3TE_ENVIRONMENT This is the environment key from your s3te.config.json, for example test or prod. For repositories with multiple environments, set this variable or pass the environment manually when starting workflow_dispatch.
  • AWS region You do not need to copy this into GitHub. The workflow reads environments.<name>.awsRegion directly from s3te.config.json.

What gets uploaded where:

  • For each variant, S3TE stages partDir into part/ and sourceDir into <variant>/.
  • Then S3TE syncs that staged tree into the resolved code bucket for that variant and environment.
  • In the deployed runtime, files below <variant>/ whose extension is in rendering.renderExtensions are rendered. By default that means .html, .htm and .part.
  • Every other file below <variant>/ is copied unchanged into the public output bucket, with the <variant>/ prefix removed. Dot-folders such as .well-known/, image folders such as gfx/, files like site.webmanifest, and root-level icons inside sourceDir are valid assets.
  • Empty folders are not published, because S3 stores objects, not real directories.

With your example config this means:

  • test + website: app/part and app/website go to test-website-code-sop
  • test + app: app/part-app and app/app go to test-app-code-sop
  • prod + website: app/part and app/website go to website-code-sop
  • prod + app: app/part-app and app/app go to app-code-sop

For the prod + app case, these source files publish like this:

  • app/app/index.html is rendered from app-code-sop:app/index.html to app-sop:index.html
  • app/app/site.webmanifest is copied from app-code-sop:app/site.webmanifest to app-sop:site.webmanifest
  • app/app/.well-known/assetlinks.json is copied to app-sop:.well-known/assetlinks.json
  • app/app/gfx/logo.svg is copied to app-sop:gfx/logo.svg

Minimal IAM policy example for the test environment and both variants:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::test-website-code-sop",
        "arn:aws:s3:::test-app-code-sop"
      ]
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": [
        "arn:aws:s3:::test-website-code-sop/*",
        "arn:aws:s3:::test-app-code-sop/*"
      ]
    }
  ]
}

For different environments or additional variants, use the derived code bucket names from your config.

The scaffolded workflow looks like this:

# Required GitHub repository secrets:
# - AWS_ACCESS_KEY_ID
# - AWS_SECRET_ACCESS_KEY
# Required GitHub repository variable for push-based sync in multi-environment projects:
# - S3TE_ENVIRONMENT (for example dev, test, or prod)
# Optional GitHub repository variable:
# - S3TE_GIT_BRANCH (defaults to main)
# Notes:
# - workflow_dispatch can override the environment manually
# - if s3te.config.json contains exactly one environment, no S3TE_ENVIRONMENT variable is needed
# This workflow reads s3te.config.json at runtime and syncs all variants into their own code buckets.
name: S3TE Sync
on:
  workflow_dispatch:
    inputs:
      environment:
        description: Optional S3TE environment override from s3te.config.json
        required: false
        type: string
  push:
    paths:
      - "app/**"
      - "package.json"
      - "package-lock.json"
      - ".github/workflows/s3te-sync.yml"

jobs:
  sync:
    if: github.event_name == 'workflow_dispatch' || github.ref_name == (vars.S3TE_GIT_BRANCH || 'main')
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - name: Install dependencies
        shell: bash
        run: |
          if [ -f package-lock.json ]; then
            npm ci
          else
            npm install
          fi
      - name: Resolve S3TE environment and AWS region from s3te.config.json
        id: s3te-config
        shell: bash
        env:
          WORKFLOW_INPUT_ENVIRONMENT: ${{ inputs.environment }}
          REPOSITORY_S3TE_ENVIRONMENT: ${{ vars.S3TE_ENVIRONMENT }}
        run: |
          node -e "const fs=require('node:fs'); const fromInput=(process.env.WORKFLOW_INPUT_ENVIRONMENT || '').trim(); const fromVariable=(process.env.REPOSITORY_S3TE_ENVIRONMENT || '').trim(); const config=JSON.parse(fs.readFileSync('s3te.config.json','utf8')); const known=Object.keys(config.environments ?? {}); const requested=(fromInput || fromVariable || (known.length === 1 ? known[0] : '')).trim(); if(!requested){ console.error('Missing S3TE environment. Provide workflow_dispatch input \"environment\" or set GitHub repository variable S3TE_ENVIRONMENT. Known environments: ' + (known.length > 0 ? known.join(', ') : '(none)') + '.'); process.exit(1);} const environmentConfig=config.environments?.[requested]; if(!environmentConfig){ console.error('Unknown environment ' + requested + '. Known environments: ' + (known.length > 0 ? known.join(', ') : '(none)') + '.'); process.exit(1);} fs.appendFileSync(process.env.GITHUB_OUTPUT, 'environment=' + requested + '\n'); fs.appendFileSync(process.env.GITHUB_OUTPUT, 'aws_region=' + environmentConfig.awsRegion + '\n');"
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ steps.s3te-config.outputs.aws_region }}
      - run: npx s3te validate --env ${{ steps.s3te-config.outputs.environment }}
      - run: npx s3te sync --env ${{ steps.s3te-config.outputs.environment }}

Usage

Once the project is installed, your everyday loop splits into two paths: deploy when infrastructure changes, sync when only project sources changed.

Daily Workflow

  1. Edit files in app/part/ and app/website/.
  2. If you use content-driven tags without Webiny, edit offline/content/en.json or offline/content/items.json.
  3. If you use Webiny and want the current mirrored live content locally, download the content snapshot first.
  4. Validate and render locally.
  5. Run your tests.
  6. Use deploy for the first installation or after infrastructure/config/runtime changes.
  7. Use sync for day-to-day source publishing into the code buckets.
npx s3te download-content --env dev
npx s3te validate
npx s3te render --env dev
npx s3te test
npx s3te sync --env dev

If you are not using Webiny yet, skip download-content and keep editing the local JSON files directly.

Use a full deploy only when needed:

npx s3te deploy --env dev

Once Webiny is installed and the stack is deployed with Webiny enabled, CMS content changes are picked up in AWS through the DynamoDB stream integration. Those content changes do not require another sync or deploy.

CLI Commands

| Command | What it does | | --- | --- | | s3te init | Creates the starter project structure and base config. | | s3te validate | Checks config and template syntax without rendering outputs. | | s3te render --env <name> | Renders locally into offline/S3TELocal/preview/<env>/.... | | s3te test | Runs the project tests from offline/tests/. | | s3te download-content --env <name> | Downloads the mirrored S3TE content table into offline/content/items.json for local render and test runs. | | s3te package --env <name> | Builds the AWS deployment artifacts without deploying them yet. | | s3te sync --env <name> | Uploads current project sources into the configured code buckets. | | s3te doctor --env <name> | Checks local machine and AWS access before deploy. | | s3te deploy --env <name> | Deploys or updates the AWS environment and syncs source files. | | s3te option <webiny|sitemap> | Writes or updates optional feature configuration such as Webiny or sitemap support in an existing S3TE project. |

Template Commands

S3TE uses literal HTML-like tags inside your .html and .part files. The tags are case-sensitive, always lowercase, and never use attributes. JSON-based commands must contain valid JSON and reject unknown properties.

The commands below are grouped by purpose:

  • Core Features work in every S3TE project.
  • Webiny Features are the content-driven commands. Despite the name, they also work with local content files under offline/content/ when you are not using Webiny yet.

Core Features

Action

Loads another template fragment from the current variant's partDir, renders it recursively, and inserts the result at the current position.

Syntax

<part>head.part</part>

The payload must be a relative path inside partDir. Leading / and .. are invalid.

Example

<head>
  <part>head.part</part>
</head>

Action

Evaluates one inline JSON rule and renders its template only when the rule matches the current render target.

Syntax

<if>{
  "env": "prod",
  "file": "index.html",
  "not": false,
  "template": "<meta name='robots' content='all'>"
}</if>

Supported JSON properties:

  • env optional: matches the current environment name case-insensitively
  • file optional: matches the current output filename, for example index.html
  • not optional: inverts the final result when true
  • template required: inline HTML to render when the rule matches

If both env and file are present, both must match.

If both conditions are omitted, the tag behaves like an inline template include and always renders its template.

Example

<if>{
  "env": "prod",
  "template": "<meta name='robots' content='all'>"
}</if>
<if>{
  "env": "test",
  "template": "<meta name='robots' content='noindex'>"
}</if>

Action

Prints metadata of the file currently being rendered.

Syntax

<fileattribute>filename</fileattribute>

Currently supported values:

  • filename: the output key relative to the target bucket, for example news/article-one.html

Example

<link rel="canonical" href="https://<lang>baseurl</lang>/<fileattribute>filename</fileattribute>">

Action

Prints language-related metadata of the current render target.

Syntax

<lang>2</lang>
<lang>baseurl</lang>

Currently supported values:

  • 2: the current language code such as en or de
  • baseurl: the resolved base hostname for the current language and environment

Example

<html lang="<lang>2</lang>">
  <head>
    <link rel="canonical" href="https://<lang>baseurl</lang>">
  </head>
</html>

Action

Selects the block whose tag name matches the current language and renders only that block.

Syntax

<switchlang>
  <de>Willkommen</de>
  <en>Welcome</en>
</switchlang>

There is no fallback to defaultLanguage. If the current language block is missing, S3TE renders an empty string and records a warning.

Example

<p>
  <switchlang>
    <de>Dein ultimatives Website-Werkzeug</de>
    <en>Your ultimate website tool</en>
  </switchlang>
</p>

Webiny Features

These commands read from the resolved content repository. With Webiny enabled, that means mirrored Webiny content. Without Webiny, the same commands can read from local JSON files under offline/content/.

Action

Loads a single content item by contentId and inserts its content fragment.

S3TE first tries the language-specific field content&lt;lang&gt;, for example contentde, and falls back to content if the language-specific field does not exist.

Syntax

<dbpart>impressum</dbpart>

The payload is the content ID, not the internal database record ID.

Example

<body>
  <dbpart>impressum</dbpart>
</body>

Action

Queries matching content items and renders the given inline template once for each result.

Syntax

<dbmulti>{
  "filter": [
    {"forWebsite": {"BOOL": true}}
  ],
  "filtertype": "equals",
  "limit": 3,
  "template": "<article><h2><dbitem>headline</dbitem></h2></article>"
}</dbmulti>

Supported JSON properties:

  • filter required: array of legacy DynamoDB-style filter clauses
  • filtertype optional: equals or contains, default is equals
  • limit optional: maximum number of items to render
  • template required: inline template rendered once per match

Filter notes:

  • every filter clause contains exactly one field
  • multiple clauses are combined with logical AND
  • __typename matches the content model, for example article
  • supported legacy value wrappers are S, N, BOOL, NULL, and L
  • results are sorted deterministically; items with a numeric field order are rendered first in ascending order, then items without a numeric order, then contentId, then id
  • this also works with mirrored Webiny content if the model contains a field with the exact field ID order and type number

Example

<dbmulti>{
  "filter": [
    {"__typename": {"S": "article"}},
    {"forWebsite": {"BOOL": true}}
  ],
  "limit": 3,
  "template": "<a href='article-<dbitem>slug</dbitem>.html'><h2><dbitem>headline</dbitem></h2></a>"
}</dbmulti>

Action

Turns one source template into multiple output files. The content items are selected by filter, and each item produces one rendered file.

Syntax

<dbmultifile>{
  "filenamesuffix": "slug",
  "filter": [
    {"__typename": {"S": "article"}}
  ],
  "limit": 10
}</dbmultifile>
<!doctype html>
<html>
  <body>
    <h1><dbmultifileitem>headline</dbmultifileitem></h1>
  </body>
</html>

Supported JSON properties:

  • filenamesuffix required: field whose value becomes the filename suffix
  • filter required: array of legacy DynamoDB-style filter clauses
  • filtertype optional: equals or contains, default is equals
  • limit optional: maximum number of files to generate

Rules:

  • dbmultifile must be the first non-whitespace construct in the file
  • the control block itself is not part of the output
  • generated filenames follow the pattern <basename>-<suffix>.<ext>
  • the suffix must not be empty and must not contain /, \\, or :
  • suffixes must be unique within that template

Example

If the source file is article.html and the current item has "slug": "first-article", the generated output becomes article-first-article.html.

<dbmultifile>{
  "filenamesuffix": "slug",
  "filter": [
    {"__typename": {"S": "article"}}
  ]
}</dbmultifile>
<article>
  <h1><dbmultifileitem>headline</dbmultifileitem></h1>
</article>

Action

Reads one field from the current content item. This works inside dbmulti templates and inside dbmultifile bodies.

Syntax

<dbitem>headline</dbitem>

Special field names:

  • __typename
  • contentId
  • id
  • locale
  • tenant
  • _version
  • _lastChangedAt

For the field name content, S3TE again prefers content&lt;lang&gt; over content.

If the field value is a string array, S3TE serializes it as concatenated HTML links.

Example

<dbmulti>{
  "filter": [{"__typename": {"S": "article"}}],
  "template": "<article><h2><dbitem>headline</dbitem></h2><div><dbitem>content</dbitem></div></article>"
}</dbmulti>

Action

Reads one field from the current content item and can apply one transformation mode. It is primarily meant for dbmultifile bodies, but works wherever a current content item exists.

Syntax

Simple field output:

<dbmultifileitem>headline</dbmultifileitem>

JSON command mode:

<dbmultifileitem>{"field":"content","limit":160}</dbmultifileitem>

Supported JSON properties:

  • field required
  • limit optional: truncate text to a maximum length and append ...
  • limitlow optional: choose a random length between limitlow and limit
  • format optional: currently only date
  • locale optional: used with format: "date"
  • divideattag optional: cut a section out of the field value
  • startnumber optional: 1-based occurrence number for the divide start
  • endnumber optional: 1-based occurrence number for the divide end

Only one transform mode is allowed at a time:

  • limit mode: limit with optional limitlow
  • date mode: format: "date"
  • divide mode: divideattag

Date mode formats de as dd.mm.yyyy. All other locales currently format as mm/dd/yyyy.

Examples

Simple field output:

<dbmultifileitem>headline</dbmultifileitem>

Truncated teaser text:

<dbmultifileitem>{"field":"content","limit":160}</dbmultifileitem>

Date formatting:

<dbmultifileitem>{"field":"publishedAt","format":"date","locale":"de"}</dbmultifileitem>

Extract one section from a larger HTML field:

<dbmultifileitem>{
  "field":"content",
  "divideattag":"<h2>",
  "startnumber":2,
  "endnumber":3
}</dbmultifileitem>

Optional: Sitemap

You do not need sitemap.xml automation to use S3TE. If you want it, S3TE can maintain one sitemap.xml per published output bucket through a dedicated Lambda, just like the older 2.x generation.

Add this block to s3te.config.json:

"integrations": {
  "sitemap": {
    "enabled": true,
    "environments": {
      "dev": {
        "enabled": false
      }
    }
  }
}

The top-level enabled acts as the default. integrations.sitemap.environments.<env>.enabled can override that for a single environment.

If you prefer the CLI path, this does the same option update:

npx s3te option sitemap --enable --write
npx s3te option sitemap --env test --enable --write

After enabling or disabling sitemap, redeploy the affected environment once:

npx s3te deploy --env prod

When sitemap is enabled for an environment, S3TE adds one sitemap-updater Lambda to that environment stack and wires every output bucket to it for HTML create/delete events.

The Lambda maintains sitemap.xml directly inside the same output bucket:

  • one sitemap per variant/language output bucket
  • only published HTML files are tracked
  • 404.html is ignored
  • index.html becomes https://example.com/
  • nested news/index.html becomes https://example.com/news/
  • regular pages such as about.html stay https://example.com/about.html

Because the trigger sits on the output bucket, the sitemap also stays correct when HTML is regenerated from Webiny content changes in AWS. Asset-only changes do not affect it.

Optional: Webiny CMS

You do not need Webiny to use S3TE. Start with plain HTML first. Add Webiny only when editors should maintain content in a CMS instead of editing local JSON files under offline/content/.

The supported target for this optional path is Webiny 6.x on its standard AWS deployment model.

Important for the Webiny path: S3TE does not turn on DynamoDB Streams on your Webiny table for you. You must enable the stream manually on the Webiny source table. S3TE uses that stream as the trigger source for CMS-driven rerendering.

S3TE with Webiny

This section assumes that S3TE is already installed and deployed. The S3TE-specific Webiny setup only starts after you already have a running Webiny 6.x installation in AWS.

  1. Install Webiny in AWS and finish the Webiny setup first.
  2. Find the Webiny DynamoDB table that contains the CMS entries you want S3TE to mirror.
  3. Manually enable DynamoDB Streams on that Webiny table before the first S3TE deploy with Webiny enabled. Use NEW_AND_OLD_IMAGES. Without that stream, s3te deploy --env <name> cannot wire the Webiny trigger and fails because the table has no LatestStreamArn.
  4. Write the Webiny option into your existing S3TE config:
npx s3te option webiny --enable --source-table webiny-1234567 --tenant root --model article --write

staticContent and staticCodeContent are kept automatically. Add --model once per custom model you want S3TE to mirror.

--model article means:

  • article is the technical Webiny model ID, not the human-readable label shown in the CMS UI.
  • S3TE adds that model ID to integrations.webiny.relevantModels in s3te.config.json.
  • Only Webiny stream records whose model is listed in relevantModels are mirrored into the S3TE content table and can trigger rerendering.
  • If you omit --model, only the built-in defaults staticContent and staticCodeContent are mirrored.
  • You can pass the flag multiple times for multiple models, for example --model article --model news --model event.

That makes the option example above equivalent to a config that contains:

"relevantModels": ["article", "staticContent", "staticCodeContent"]

Use this for every Webiny model whose entries should be available to S3TE template commands like dbitem, dbmulti, dbmultifile, dbmultifileitem, or dbpart.

If a Webiny model should control the output order for dbmulti or dbmultifile, add a field with:

  • field ID order
  • field type number

S3TE sorts matching items by that numeric order in ascending order. Items without a numeric order stay at the end.

If different environments should read from different Webiny installations or tenants, run the option command per environment:

npx s3te option webiny --env test --enable --source-table webiny-test-1234567 --tenant preview --write
npx s3te option webiny --env prod --enable --source-table webiny-live-1234567 --tenant root --write
  1. Verify again that DynamoDB Streams are enabled on the Webiny source table with NEW_AND_OLD_IMAGES. You enable this manually in the AWS console on the Webiny DynamoDB table under Exports and streams. S3TE creates the Lambda event source mapping during deploy, but it does not create or enable the table stream itself.
  2. If your S3TE language keys are not identical to your Webiny locales, add webinyLocale per language in s3te.config.json, for example "en": { "webinyLocale": "en-US" }.
  3. If your Webiny installation hosts multiple tenants, keep integrations.webiny.tenant set so S3TE only mirrors the intended tenant.
  4. Check the project again:
npx s3te doctor --env prod
  1. Redeploy the existing S3TE environment:
npx s3te deploy --env prod

That deploy updates the existing environment stack and, when Webiny is enabled, also deploys the separate Webiny option stack for content-mirror and its DynamoDB stream mapping. You do not need a fresh S3TE installation. After that, Webiny content changes flow through the deployed AWS resources automatically; only template or asset changes still need s3te sync --env <name>.

  1. To test current live CMS content locally, pull the mirrored S3TE content table into the local fixture folder before rendering or running tests:
npx s3te download-content --env prod
npx s3te render --env prod
npx s3te test

download-content reads the mirrored S3TE content table, keeps only the newest mirrored item per contentId and locale, and writes the result to offline/content/items.json. The normal local dbpart, dbmulti, dbmultifile, dbitem, and dbmultifileitem flow then reads from that file.

Manual versus automatic responsibilities in this step:

  • Manual: enable DynamoDB Streams on the Webiny source table
  • Automatic during s3te deploy: read LatestStreamArn, create the Lambda event source mapping, and deploy the S3TE Webiny mirror Lambda in the separate Webiny option stack

The option command writes or updates the integrations.webiny block in s3te.config.json. A typical result looks like this:

Example config block:

"integrations": {
  "webiny": {
    "enabled": true,
    "sourceTableName": "webiny-1234567",
    "mirrorTableName": "{stackPrefix}_s3te_content_{project}",
    "tenant": "root",
    "relevantModels": ["article", "staticContent", "staticCodeContent"],
    "environments": {
      "test": {
        "sourceTableName": "webiny-test-1234567",
        "tenant": "preview"
      },
      "prod": {
        "sourceTableName": "webiny-live-1234567",
        "tenant": "root"
      }
    }
  }
}

For localized Webiny projects, the language block can also carry the mapping explicitly:

"languages": {
  "en": {
    "baseUrl": "example.com",
    "cloudFrontAliases": ["example.com"],
    "webinyLocale": "en-US"
  }
}

The content-driven tags are documented in Template Commands, section Webiny Features.