Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Wechat Work Menu Bundle Laravel Package

tourze/wechat-work-menu-bundle

A Symfony bundle for managing WeChat Work (企业微信) custom menus. Register menu structures through Symfony configuration and services, then create, sync, and delete menus via the WeChat Work API—keeping application menus versioned in code instead of configured by hand in the admin console.

View on GitHub
Deep Wiki
Context7

Getting Started

What this package is (as far as 0.0.1 tells us): tourze/wechat-work-menu-bundle is an early-stage wrapper around the WeChat Work (企业微信) application menu API — the endpoints that let you create, read, and delete the custom menu shown inside a self-built app (agent). It handles menu payload construction and the authenticated calls to cgi-bin/menu/create, cgi-bin/menu/get, and cgi-bin/menu/delete.

The 0.0.1 release has no changelog beyond a commit link, so treat the source as the documentation. Budget 20–30 minutes reading src/ before wiring anything up.

Minimum steps:

  1. composer require tourze/wechat-work-menu-bundle:0.0.1 — pin the exact version, not ^0.0.1.
  2. Read composer.json first. The -bundle suffix strongly suggests a Symfony bundle, not a Laravel package. Check for type: symfony-bundle, a DependencyInjection/ directory, and whether an Illuminate\Support\ServiceProvider exists at all.
  3. Collect your credentials before touching code: corp ID, agent ID, and the app secret. The agent must actually have menu permission in the admin console.
  4. Add the corp IPs to the app's trusted IP list — WeChat Work rejects token requests from unlisted IPs, and the error surfaces late.
  5. First use case: define a two-level menu as a plain PHP array and push it once via a throwaway route or php artisan tinker, then confirm it renders in the mobile app.

Where to look first: the class that issues the HTTP request (usually named *MenuClient, *MenuService, or *Api). That single file tells you the config shape, the error handling contract, and whether token acquisition is delegated or baked in.

Implementation Patterns

Wrap it, don't sprinkle it. At 0.0.x the public API will move. Put the package behind your own MenuPublisher interface with one method (publish(array $menu): void) and keep the vendor class out of your domain code.

Config-driven menu definition. Keep the menu tree in config/wechat-menu.php rather than inline in a service. It changes for business reasons (campaigns, feature flags), not code reasons, and you want it diffable in git:

return [
    'buttons' => [
        ['type' => 'view', 'name' => '商城', 'url' => env('SHOP_URL')],
        ['type' => 'click', 'name' => '客服', 'key' => 'CONTACT'],
        ['name' => '更多', 'sub_button' => [
            ['type' => 'miniprogram', 'name' => '小程序', 'appid' => '...', 'pagepath' => 'pages/index'],
        ]],
    ],
];

Publish via an Artisan command, not on every request. Menu creation is a write against a rate-limited, token-authenticated API. Create wechat:menu:push and run it manually or in deploy:

Artisan::command('wechat:menu:push', function (MenuPublisher $publisher) {
    $publisher->publish(config('wechat-menu.buttons'));
    $this->info('Menu pushed.');
});

Push on deploy, not on boot. A service provider that pushes on every php artisan invocation will burn your daily token quota and slow down local commands. Guard any auto-push behind app()->runningInConsole() and an explicit env flag.

Separate menu trees per environment. Point WECHAT_AGENT_ID / WECHAT_SECRET at a sandbox corp locally. Pushing a test menu into the production agent is a real incident — the menu changes for every user in the org.

Read-back for verification. After publishing, call the get-menu endpoint and assert the returned tree matches what you sent. WeChat Work silently drops or reorders buttons that violate limits, so a 200 response is not proof of success.

Testing: bind a fake implementation of your MenuPublisher interface in the container and assert on the array passed in. Don't hit the real API in tests — token acquisition is stateful and will race across parallel test workers.

Gotchas and Tips

0.0.1 means the API is not stable. Pin 0.0.1 exactly, wrap the dependency, and read the diff before every bump. A 0.0.x package can rename its main class between patches without a deprecation cycle.

Check errcode, not the HTTP status. The WeChat Work API returns HTTP 200 with {"errcode": 40001, "errmsg": "invalid credential"} in the body. If the package doesn't throw on non-zero errcode, you'll ship a "successful" deploy with no menu. Verify this explicitly — it's the #1 source of silent breakage in WeChat integrations.

Token lifecycle is shared and single-flight. access_token is valid ~7200s and requesting a new one can invalidate the previous one for a short window. If the package doesn't cache the token, two concurrent requests will fight. If it does cache, confirm the cache store is shared across your app servers — the default array/file store will cause thrashing in a multi-instance deployment.

Hard limits you will hit:

  • Max 3 top-level buttons; max 5 sub-buttons per top-level button.
  • Sub-buttons cannot nest further — two levels only.
  • name is measured in bytes: 16 bytes for top-level, 40 for sub-buttons. Chinese characters are 3 bytes in UTF-8, so roughly 5 and 13 characters respectively. Exceeding this fails at publish time, not at config-parse time — so validate in your own code first.

Symfony bundle ≠ Laravel package. If there's no service provider, you cannot composer require and go. Options, in order of preference: (a) use it standalone by instantiating the client manually with a PSR-18 HTTP client, (b) write a thin ServiceProvider that binds the client, or (c) implement the menu push yourself — it's ~100 lines against the documented API and removes the dependency entirely. Given the maturity level, (c) is a legitimate choice.

Agent ID naming is inconsistent across the WeChat API (agentid vs agentId) and the value is an integer, not a string, in payloads. Type-juggle carefully if you build the arrays yourself.

Menu updates are not instant for end users. The client caches the menu; users may see the old one for minutes and sometimes need to reopen the app. Don't treat a user report of "old menu" as a bug until you've verified via the get-menu endpoint.

Personal (/menu/create?agentid=) vs default menus are different resources. Confirm which one the package targets — mixing them up produces a menu that appears for some users and not others.

Extension points are likely thin at 0.0.1. If the package hardcodes its HTTP client, you can't inject Guzzle middleware for logging or retries — which means no visibility into failing calls. Check whether the client is constructed from a PSR-18/PSR-17 pair; if it is, register a logging middleware immediately, because when this breaks you'll have nothing to debug with otherwise.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings