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

@nytka/plugin-gtm

v0.1.2

Published

Google Tag Manager connector for nytka projects. Reads container configuration into datasets/ and registers it with provenance. Read-only.

Downloads

24

Readme

@nytka/plugin-gtm

Google Tag Manager connector for nytka projects. Reads one container's live configuration into the project's datasets/ and registers it in datasets/index.json with provenance.

This is the READ half only. Creating and editing tags is a separate, later task (PLG-011's write half), gated on a second, write-capable service account that does not exist yet (OPS-003). This package requests only tagmanager.readonly and is read-only by construction — see Read-only, and how it is held below, not merely by convention.

Install

npm install @nytka/plugin-gtm

Two dependencies. @googleapis/tagmanager, 2.16 MB unpacked — the same per-API split @nytka/plugin-gsc uses for Search Console, verified 2026-07-28 against the npm registry. It depends only on googleapis-common: no google-gax, so none of the rimraf/glob/minimatch/brace-expansion advisory chain PLG-005 could not fully clear for @nytka/plugin-ga4. And @nytka/core, which has no dependencies of its own.

Setup

Same credential every other Google connector in this line shares — if a project already has @nytka/plugin-gsc or @nytka/plugin-ga4 configured, the only new step is granting this service account access inside Tag Manager itself (step 3 below).

1. Enable the API

https://console.cloud.google.com/apis/library/tagmanager.googleapis.com → Enable.

Per Cloud project, same as every other Google API used here. A project already enabled for Search Console or Analytics has not enabled this one — it is a separate switch, and the first symptom of skipping it is a 403 that reads like a permissions problem when it is really "this API is off" (see decisions/0008, which found the identical trap while confirming GTM accepts service accounts at all).

2. Reuse or create the service account

If GOOGLE_SERVICE_ACCOUNT_KEY is already set for this project (GSC or GA4 is installed), reuse that same key file — do not create a second one. Skip to step 3.

Otherwise: IAM & Admin → Service accounts → Create service account. Name it something like <project>-google. Skip both optional steps — no IAM role is needed. Download the key, move it into the project and rename it, exactly as @nytka/plugin-gsc's README describes in full.

3. Grant it access in Tag Manager

Cloud IAM does not grant Tag Manager access — the account must be added inside GTM itself.

grep client_email <project>/private/google-service-account.json

https://tagmanager.google.com/ → Admin → User Management → + → paste the email. Grant Read permission on the account (or on one container, if that is all this project needs). Never grant Publish or Edit to this account — it is the read credential, and 0008 requires the two to stay separable by more than a scope request.

4. Configure

One .env at the project root. Every key for the project lives there and nowhere else.

The name below ships with the package too, so recovering it later never means re-reading this guide:

cat node_modules/@nytka/plugin-gtm/.env.example >> .env
GOOGLE_SERVICE_ACCOUNT_KEY=private/google-service-account.json

A path, resolved from the project root, not the cwd — so it works from any subdirectory, same as every other Google connector here.

5. Verify

npx nytka-gtm discover

Listing an account and its containers means every step above worked.

Use

npx nytka-gtm discover                                  # auth smoke test — accounts and containers
npx nytka-gtm collect --account 6009123456 --container 123456789
npx nytka-gtm collect --account 6009123456 --container 123456789 --workspace 9
npx nytka-gtm collect --account 6009123456 --container 123456789 --no-register

discover

Lists every account and container this service account can read. Writes no files and registers no dataset — it exists purely to prove the credential works and to hand you the account and container ids collect needs.

collect

Collects one container's live configuration — not an observation over a date range, because there is none. This connector reports what is configured to happen, not what happened; that is also why there is no monthly command here the way there is in @nytka/plugin-gsc and @nytka/plugin-ga4 — nothing here aggregates over time.

What it collects:

  • the container record itself (name, public id)
  • every workspace on the container
  • for the default workspace — the numerically lowest workspaceId, which the API never flags explicitly but is, for the overwhelming majority of containers, the original "Default Workspace" GTM created it with. --workspace ID overrides this for a project that deliberately tracks a different one.
    • every tag
    • every trigger
    • every variable
    • every built-in variable enabled on the container
  • the container's version headers — its full publish history, by header only (name, numbers of each entity type, whether it was deleted) — plus, separately, whichever version is currently live, so the registry entry can name the published version number without walking the whole history to find it.

Programmatic:

import { run, discoverAccounts, collectContainer } from '@nytka/plugin-gtm'

const { id, counts, rawPath } = await run({ accountId: '6009123456', containerId: '123456789' })

run() returns counts and paths, never the tags, triggers or variables themselves — the same rule every connector in this line holds, because a container's tag configuration is exactly the kind of thing a script should query, not something an agent should hold in context.

What it writes

| Path | Committed? | |---|---| | datasets/payloads/<id>.json | no — the directory ships its own .gitignore | | datasets/index.json | yes — one entry, added or replaced |

The dataset id — question-keyed, not version-keyed

gtm-6009123456-123456789-config
    └────┬────┘ └───┬───┘
     accountId   containerId

The id names the question this dataset answers — "what is configured on this container" — never the answer. That is the same rule @nytka/plugin-sanity's datasetId established for a content lake with no reporting period, applied here for the same reason: a container's configuration is one fact that changes, not a series of observations, so there is no reporting period to key on the way @nytka/plugin-gsc and @nytka/plugin-ga4's monthly ids do.

registerDataset matches on id, so re-collecting the same container after a publish replaces this entry rather than adding a second one describing the same container a moment apart.

GTM does have something Sanity's content lake does not: a published container version is a real, immutable, user-declared number — not an incidental hash of whatever happened to be in the lake at request time. That number is worth keeping, and it is recorded in the registry entry's publishedVersionId field, not in the id. Putting it in the id would fragment one container's history into one registry entry per publish, the same drift PLG-007 exists to prevent for monthly series, just approached from the opposite direction — an id that would move too often rather than one that used to move too little.

Read-only, and how it is held

@googleapis/tagmanager's generated client is one object carrying every RPC the API has — including tags.create, tags.delete, workspaces.create_version and versions.publish. Object.freeze()-ing that object would not remove any of those methods; freezing only stops new properties being added; it has no opinion about the ones already there.

So this package never hands that generated client back to a caller, frozen or otherwise. client() returns a new object, built once inside src/gtm.mjs, exposing only the calls discover and collect need — every one of them a list or a get. The generated client is a variable closed over inside that function and is never a property of the returned object, so there is no path from the handle back to anything that could spell a mutation. The returned object is frozen on top of that, so a caller cannot bolt a write method back on either.

This is the same shape @nytka/plugin-sanity uses for a fetch-based client with no method surface to hide — a frozen handle exposing only what a read needs, with the real machinery closed over rather than attached. test/readonly.test.mjs checks it from both directions, matching that package's suite: every key on the handle is a function whose name reads as a read, nothing on it is itself an object a caller could reach into, and the two scope literals tagmanager.publish and tagmanager.edit.containers do not appear anywhere in src/ — not even in a comment, so there is nothing here that could spell them by accident either.

Missing credential

If GOOGLE_SERVICE_ACCOUNT_KEY is not set, both discover and collect name the variable and exit 0 — this project has not configured Google Tag Manager access, which is a normal, unconfigured state, not a broken package (decisions/0008 §0: capability is configuration presence, and absence must produce a plain answer, not a stack trace).

Rules it follows

  • Payloads never enter agent context. Query them with a script; write conclusions to research/.
  • The project is found by walking up for project.yaml, so it works at any install depth.
  • Pagination follows nextPageToken to exhaustion on every list call — accounts, containers, workspaces, tags, triggers, variables, built-in variables and version headers all page independently.
  • No YAML parsing, no config file. Secrets from .env, everything else from flags.

Tests

npm test

node --test against fake handles matching the same shape client() returns — no network, no credential, no client data. Covers pagination to exhaustion on every list call, the dataset id rule (question-keyed, replacing rather than accumulating across a publish), the missing-credential exit-0 behaviour against the actual CLI process, the registry entry shape, and the read-only construction itself.