Prompt: "Download the credentials JSON file and save it in your project root."
Step 3.3: Save Credentials
Ask user for downloaded file path:
→ "Where did you save the credentials file?"
Options:
a) File is in project root → Look for oauth2.keys.json or similar
b) User provides path → Copy to gtm-credentials.json
c) User pastes content → Write to gtm-credentials.json
Validate JSON structure:
- Has "installed" or "web" key
- Has "client_id", "client_secret", "redirect_uris"
If invalid → Error with specific issue
If valid → Save as gtm-credentials.json
Phase 4: GTM Account & Container Configuration
Step 4.1: Gather GTM Information
Guide user to find GTM details:
"Open Google Tag Manager: https://tagmanager.google.com"
Q1: What is your GTM Account ID?
→ Hint: In GTM, go to Admin. Account ID is shown at top (format: 1234567890)
Q2: What is your GTM Container ID?
→ Hint: In GTM, select container. Container ID shown at top (format: GTM-XXXXXX)
Validate inputs:
- Account ID: Must be numeric
- Container ID: Must start with "GTM-"
Step 4.2: Create Configuration File
Generate gtm-config.json:
{
"accountId": "[user input]",
"containerId": "[user input]",
"containerPublicId": "[user input]"
}
Save to project root.
Note: workspaceId is NOT stored in gtm-config.json. The gtm-implementation skill always resolves the active workspace dynamically via the GTM API. This prevents breakage after you publish a version in the GTM UI, which deletes and recreates the workspace.
Phase 5: OAuth Authorization
Step 5.1: Generate Auth URL
Using googleapis library, generate OAuth consent URL.
Present to user:
"
=== Authorization Required ===
Open this URL in your browser to authorize access:
[Long Google OAuth URL]
This will:
1. Ask you to sign in to Google
2. Show permissions requested (Read/Write GTM access)
3. Redirect to a localhost URL with an authorization code
After authorizing, you'll see a page that says:
'The authentication flow has completed. You may close this window.'
'\nAfter authorization, copy the full redirect URL from your browser.\n'
)
;
const
rl
=
readline
.
createInterface
(
{
input
:
process
.
stdin
,
output
:
process
.
stdout
,
}
)
;
rl
.
question
(
'Paste the redirect URL here: '
,
(
redirectUrl
)
=>
{
rl
.
close
(
)
;
// Extract code from URL
const
url
=
new
URL
(
redirectUrl
)
;
const
code
=
url
.
searchParams
.
get
(
'code'
)
;
if
(
!
code
)
{
console
.
error
(
'✗ No authorization code found in URL'
)
;
process
.
exit
(
1
)
;
}
// Exchange code for token
oAuth2Client
.
getToken
(
code
,
(
err
,
token
)
=>
{
if
(
err
)
{
console
.
error
(
'✗ Error retrieving access token:'
,
err
)
;
process
.
exit
(
1
)
;
}
// Save token
fs
.
writeFileSync
(
TOKEN_PATH
,
JSON
.
stringify
(
token
,
null
,
2
)
)
;
console
.
log
(
'\n✓ Token saved to'
,
TOKEN_PATH
)
;
console
.
log
(
'\n=== Authorization Complete ===\n'
)
;
console
.
log
(
'You can now use the GTM API.'
)
;
console
.
log
(
'\nIMPORTANT: Add gtm-token.json to .gitignore\n'
)
;
}
)
;
}
)
;
scripts/test-connection.js
// Test GTM API connection
const
{
google
}
=
require
(
'googleapis'
)
;
const
fs
=
require
(
'fs'
)
;
const
path
=
require
(
'path'
)
;
const
TOKEN_PATH
=
path
.
join
(
process
.
cwd
(
)
,
'gtm-token.json'
)
;
const
CREDENTIALS_PATH
=
path
.
join
(
process
.
cwd
(
)
,
'gtm-credentials.json'
)
;
const
CONFIG_PATH
=
path
.
join
(
process
.
cwd
(
)
,
'gtm-config.json'
)
;
// Check files exist
if
(
!
fs
.
existsSync
(
CREDENTIALS_PATH
)
)
{
console
.
error
(
'✗ gtm-credentials.json not found'
)
;
process
.
exit
(
1
)
;
}
if
(
!
fs
.
existsSync
(
TOKEN_PATH
)
)
{
console
.
error
(
'✗ gtm-token.json not found. Run oauth-authorize.js first.'
)
;
process
.
exit
(
1
)
;
}
if
(
!
fs
.
existsSync
(
CONFIG_PATH
)
)
{
console
.
error
(
'✗ gtm-config.json not found'
)
;
process
.
exit
(
1
)
;
}
// Load files
const
credentials
=
JSON
.
parse
(
fs
.
readFileSync
(
CREDENTIALS_PATH
,
'utf8'
)
)
;
const
token
=
JSON
.
parse
(
fs
.
readFileSync
(
TOKEN_PATH
,
'utf8'
)
)
;
const
config
=
JSON
.
parse
(
fs
.
readFileSync
(
CONFIG_PATH
,
'utf8'
)
)
;
const
{
client_secret
,
client_id
,
redirect_uris
}
=
credentials
.
installed
||
credentials
.
web
;
// Create OAuth client
const
oAuth2Client
=
new
google
.
auth
.
OAuth2
(
client_id
,
client_secret
,
redirect_uris
[
0
]
)
;
oAuth2Client
.
setCredentials
(
token
)
;
// Create GTM client
const
tagmanager
=
google
.
tagmanager
(
{
version
:
'v2'
,
auth
:
oAuth2Client
}
)
;
console
.
log
(
'\n=== Testing GTM API Connection ===\n'
)
;
console
.
log
(
`
Account ID:
${
config
.
accountId
}
`
)
;
console
.
log
(
`
Container ID:
${
config
.
containerPublicId
}
\n
`
)
;
// Test API call
const
path_url
=
`
accounts/
${
config
.
accountId
}
/containers/
${
config
.
containerId
}
`
;
tagmanager
.
accounts
.
containers
.
get
(
{
path
:
path_url
}
)
.
then
(
response
=>
{
console
.
log
(
'✓ Connection successful!\n'
)
;
console
.
log
(
'Container details:'
)
;
console
.
log
(
`
Name:
${
response
.
data
.
name
}
`
)
;
console
.
log
(
`
Public ID:
${
response
.
data
.
publicId
}
`
)
;
console
.
log
(
`
Usage Context:
${
response
.
data
.
usageContext
.
join
(
', '
)
}
\n
`
)
;
console
.
log
(
'=== Setup Verified ===\n'
)
;
console
.
log
(
'Ready to use GTM API!\n'
)
;
}
)
.
catch
(
error
=>
{
console
.
error
(
'✗ Connection failed\n'
)
;
if
(
error
.
code
===
401
)
{
console
.
error
(
'Error: Unauthorized (401)'
)
;
console
.
error
(
'→ Token may be expired. Run oauth-authorize.js again.\n'
)
;
}
else
if
(
error
.
code
===
403
)
{
console
.
error
(
'Error: Forbidden (403)'
)
;
console
.
error
(
'→ Check that GTM API is enabled in Google Cloud Console.\n'
)
;
}
else
if
(
error
.
code
===
404
)
{
console
.
error
(
'Error: Not Found (404)'
)
;
console
.
error
(
'→ Check account ID and container ID in gtm-config.json.\n'
)
;
}
else
{
console
.
error
(
'Error:'
,
error
.
message
,
'\n'
)
;
}
process
.
exit
(
1
)
;
}
)
;
References
references/google-cloud-setup.md
- Detailed Google Cloud Console setup guide with screenshots
Important Guidelines
Security Best Practices
Never commit tokens
:
Add
gtm-token.json
to
.gitignore
Tokens contain sensitive access credentials
Credentials file security
:
gtm-credentials.json
can be committed (contains no secrets for Desktop app type)
But recommend adding to
.gitignore
for extra security
Token refresh
:
Tokens expire after 1 hour
Refresh token (included) allows automatic renewal
googleapis handles refresh automatically
Common Issues
Issue
"googleapis not found"
→ Solution: Run
npm install googleapis --save
Issue
"Invalid redirect URI"
→ Solution: Ensure OAuth client type is "Desktop app", not "Web application"
Issue
"403 Forbidden"
→ Solution: Enable GTM API in Google Cloud Console
Issue
"404 Not Found"
→ Solution: Verify account ID and container ID are correct
Issue
"Token expired"
→ Solution: Run oauth-authorize.js again to get new token
Issue
"Workspace not found" / skill creates a new workspace unexpectedly
→ Cause: GTM deletes workspaces after publishing. A stored workspace ID becomes stale.
→ Solution: The skill now resolves workspace dynamically. No action needed.
Testing
After setup, verify with:
node
scripts/test-connection.js
Expected output:
✓ Connection successful!
Container details:
Name: My Website
Public ID: GTM-XXXXXX
Usage Context: web
=== Setup Verified ===
Execution Checklist
package.json exists (Node.js project)
googleapis installed
Google Cloud project created
GTM API enabled
OAuth credentials created and downloaded
gtm-credentials.json saved
GTM account ID obtained
GTM container ID obtained
gtm-config.json created
OAuth authorization completed
gtm-token.json saved
API connection tested successfully
gtm-token.json added to .gitignore
Supporting Files
examples/sample.md
- Example setup session showing each phase output and files created
Output Files
gtm-credentials.json
- OAuth 2.0 client credentials
gtm-token.json
- Access token and refresh token (SENSITIVE)
gtm-config.json
- GTM account and container configuration
Handoff
After successful setup:
✓ GTM API configured and validated
Next step: Invoke gtm-implementation skill to:
- Implement dataLayer events in your code
- Create GTM variables, triggers, and tags via API
Ready to implement? Invoke gtm-implementation skill.
Common Questions
Q: Can I use the same credentials for multiple projects?
A: Yes. Copy gtm-credentials.json to other projects. Each project needs its own token (gtm-token.json).
Q: What if I have multiple GTM containers?
A: Run setup for each container separately. Create different gtm-config.json files or use different project directories.
Q: Do I need a Google Cloud billing account?
A: No. GTM API is free to use. No billing account required.
Q: Can I revoke access later?
A: Yes. Go to
https://myaccount.google.com/permissions
and revoke access to "GTM Automation Script".
Q: What scopes does this request?
A:
tagmanager.edit.containers
- Read and write access to GTM containers. This allows creating/updating variables, triggers, tags.